From f5db7586982244c5e5823bab98614a891061c8bd Mon Sep 17 00:00:00 2001 From: Miguel Date: Thu, 14 Aug 2025 14:48:02 +0200 Subject: [PATCH] feat: Implement Plot Manager and Plot Manager Simple components with collapsible forms for plot definitions and variables configuration --- .examples/symbolics.txt | 2581 +++++++++++++++++ application_events.json | 307 +- config/data/dataset_variables.json | 12 +- config/data/plot_variables.json | 16 +- config/schema/ui/layout-demo.uischema.json | 78 - frontend/src/App.jsx | 17 +- frontend/src/components/DatasetManager.jsx | 287 -- frontend/src/components/PLCConfigModal.jsx | 100 - frontend/src/components/PlotManager.jsx | 172 +- .../src/components/PlotManager.jsx.backup | 611 ++++ frontend/src/components/PlotManagerSimple.jsx | 633 ++++ frontend/src/pages/Config.jsx | 167 -- frontend/src/pages/Dashboard.jsx | 1440 ++++++--- frontend/src/pages/DashboardNew.jsx | 58 +- frontend/src/pages/Events.jsx | 77 - frontend/src/pages/Plots.jsx | 1094 ------- frontend/src/pages/PlotsPageNew.jsx | 1013 ------- main.py | 23 +- static/css/pico.min.css | 4 - static/css/styles.css | 1400 --------- static/icons/favicon.ico | Bin 15983 -> 0 bytes static/icons/record.png | Bin 15983 -> 0 bytes .../chartjs-plugin-streaming.js | 461 --- .../js/chartjs-streaming/helpers.streaming.js | 85 - .../js/chartjs-streaming/plugin.streaming.js | 216 -- static/js/chartjs-streaming/plugin.zoom.js | 125 - static/js/chartjs-streaming/scale.realtime.js | 507 ---- static/js/config_editor.js | 541 ---- static/js/csv.js | 170 -- static/js/datasets.js | 519 ---- static/js/events.js | 185 -- static/js/main.js | 48 - static/js/plc.js | 79 - static/js/plotting.js | 2080 ------------- static/js/status.js | 299 -- static/js/streaming.js | 54 - static/js/tabs.js | 319 -- static/js/theme.js | 32 - static/js/utils.js | 64 - static/js/variables.js | 319 -- system_state.json | 6 +- templates/index.html | 825 ------ web_test/PLC S7-315 Streamer & Logger.html | 986 ------- .../SIDEL.png | Bin 83332 -> 0 bytes .../chart.js.descargar | 14 - .../chartjs-adapter-date-fns | 7 - .../chartjs-plugin-streaming.js.descargar | 378 --- .../csv.js.descargar | 170 -- .../datasets.js.descargar | 519 ---- .../events.js.descargar | 168 -- .../main.js.descargar | 49 - .../pico.min.css | 4 - .../plc.js.descargar | 79 - .../plotting.js.descargar | 1461 ---------- .../status.js.descargar | 256 -- .../streaming.js.descargar | 54 - .../styles.css | 1281 -------- .../tabs.js.descargar | 296 -- .../theme.js.descargar | 32 - .../utils.js.descargar | 64 - .../variables.js.descargar | 322 -- web_test/consola.txt | 856 ------ 62 files changed, 5334 insertions(+), 18686 deletions(-) create mode 100644 .examples/symbolics.txt delete mode 100644 config/schema/ui/layout-demo.uischema.json delete mode 100644 frontend/src/components/DatasetManager.jsx delete mode 100644 frontend/src/components/PLCConfigModal.jsx create mode 100644 frontend/src/components/PlotManager.jsx.backup create mode 100644 frontend/src/components/PlotManagerSimple.jsx delete mode 100644 frontend/src/pages/Config.jsx delete mode 100644 frontend/src/pages/Events.jsx delete mode 100644 frontend/src/pages/Plots.jsx delete mode 100644 frontend/src/pages/PlotsPageNew.jsx delete mode 100644 static/css/pico.min.css delete mode 100644 static/css/styles.css delete mode 100644 static/icons/favicon.ico delete mode 100644 static/icons/record.png delete mode 100644 static/js/chartjs-streaming/chartjs-plugin-streaming.js delete mode 100644 static/js/chartjs-streaming/helpers.streaming.js delete mode 100644 static/js/chartjs-streaming/plugin.streaming.js delete mode 100644 static/js/chartjs-streaming/plugin.zoom.js delete mode 100644 static/js/chartjs-streaming/scale.realtime.js delete mode 100644 static/js/config_editor.js delete mode 100644 static/js/csv.js delete mode 100644 static/js/datasets.js delete mode 100644 static/js/events.js delete mode 100644 static/js/main.js delete mode 100644 static/js/plc.js delete mode 100644 static/js/plotting.js delete mode 100644 static/js/status.js delete mode 100644 static/js/streaming.js delete mode 100644 static/js/tabs.js delete mode 100644 static/js/theme.js delete mode 100644 static/js/utils.js delete mode 100644 static/js/variables.js delete mode 100644 templates/index.html delete mode 100644 web_test/PLC S7-315 Streamer & Logger.html delete mode 100644 web_test/PLC S7-315 Streamer & Logger_files/SIDEL.png delete mode 100644 web_test/PLC S7-315 Streamer & Logger_files/chart.js.descargar delete mode 100644 web_test/PLC S7-315 Streamer & Logger_files/chartjs-adapter-date-fns delete mode 100644 web_test/PLC S7-315 Streamer & Logger_files/chartjs-plugin-streaming.js.descargar delete mode 100644 web_test/PLC S7-315 Streamer & Logger_files/csv.js.descargar delete mode 100644 web_test/PLC S7-315 Streamer & Logger_files/datasets.js.descargar delete mode 100644 web_test/PLC S7-315 Streamer & Logger_files/events.js.descargar delete mode 100644 web_test/PLC S7-315 Streamer & Logger_files/main.js.descargar delete mode 100644 web_test/PLC S7-315 Streamer & Logger_files/pico.min.css delete mode 100644 web_test/PLC S7-315 Streamer & Logger_files/plc.js.descargar delete mode 100644 web_test/PLC S7-315 Streamer & Logger_files/plotting.js.descargar delete mode 100644 web_test/PLC S7-315 Streamer & Logger_files/status.js.descargar delete mode 100644 web_test/PLC S7-315 Streamer & Logger_files/streaming.js.descargar delete mode 100644 web_test/PLC S7-315 Streamer & Logger_files/styles.css delete mode 100644 web_test/PLC S7-315 Streamer & Logger_files/tabs.js.descargar delete mode 100644 web_test/PLC S7-315 Streamer & Logger_files/theme.js.descargar delete mode 100644 web_test/PLC S7-315 Streamer & Logger_files/utils.js.descargar delete mode 100644 web_test/PLC S7-315 Streamer & Logger_files/variables.js.descargar delete mode 100644 web_test/consola.txt diff --git a/.examples/symbolics.txt b/.examples/symbolics.txt new file mode 100644 index 0000000..4f9b023 --- /dev/null +++ b/.examples/symbolics.txt @@ -0,0 +1,2581 @@ +"_CYCL_EXC ","OB 1 ","OB 1 ","Cycle Execution " +"_StepMove ","FC 1851 ","FC 1851 ","MIX - " +"AG_RECV ","FC 206 ","FC 206 ","AG RECEIVE " +"AG_SEND ","FC 205 ","FC 205 ","AG_SEND " +"AI_CTM304 ","PEW 844 ","WORD ","CTM304 - Product Conductivity " +"AI_CTS301 ","PEW 850 ","WORD ","CTS301 - Local Cip Return Conductivity " +"AI_CTS302 ","PEW 854 ","WORD ","CTS302 - Local Cip Delivery Conductivity " +"AI_FTM301 ","PEW 832 ","WORD ","FTM301 - Storage Tank Baialage Flow " +"AI_FTM304 ","PEW 862 ","WORD ","FTM304 - Mixer CO2 Inlet Meter " +"AI_FTM307 ","PEW 860 ","WORD ","FTM307-Nitrogen Flow " +"AI_LTM302 ","PEW 804 ","WORD ","LTM302 - Product Tank Level " +"AI_LTN301 ","PEW 818 ","WORD ","LTN301 - Deaireator Tank Level " +"AI_LTP303 ","PEW 808 ","WORD ","LTP303 - Syrup Tank Level " +"AI_PCM306 ","PEW 824 ","WORD ","PCM306 - Gas Pressure Injection " +"AI_ProductBrix ","PEW 8300 ","WORD ","Product Analizer - Product Brix " +"AI_ProductCO2 ","PEW 8320 ","WORD ","Product Analizer - Product CO2 " +"AI_ProductO2 ","PEW 838 ","WORD ","Product Analizer - Product O2 " +"AI_PTF203 ","PEW 810 ","WORD ","PTF203 - Differential Pressure " +"AI_PTM301 ","PEW 828 ","WORD ","PTM301 - CO2 Pressure after Sterile filter " +"AI_PTM302 ","PEW 830 ","WORD ","PTM302 - Carbo Water Pressure " +"AI_PTM304 ","PEW 806 ","WORD ","PTM304 - Product Tank Pressure " +"AI_PTM305 ","PEW 836 ","WORD ","PTM305 - Chiller Glycol Pressure " +"AI_PTN301 ","PEW 822 ","WORD ","PTN301 - Deox Inlet Pressure " +"AI_PTN313 ","PEW 802 ","WORD ","PTN313 - Deaireator Pressure (vacuostato) " +"AI_PTP338 ","PEW 816 ","WORD ","PTP338 - Syrup Inlet Pressure " +"AI_QTM305 ","PEW 856 ","WORD ","QTM305 " +"AI_RVM301 ","PEW 826 ","WORD ","RVM301 - Product Tank Pressure Valve " +"AI_RVN304 ","PEW 820 ","WORD ","RVN304 - Deaireation Valve " +"AI_TTM306 ","PEW 812 ","WORD ","TTM306 - Chiller Product Temperature " +"AI_TTM307 ","PEW 858 ","WORD ","TTM307 " +"AI_TTN321 ","PEW 800 ","WORD ","TTN321 - Deaireator Temperature " +"AI_TTN322 ","PEW 814 ","WORD ","TTN322 - Vacuum Pump Temperature " +"AI_TTS301 ","PEW 848 ","WORD ","TTS301 - Local Cip Return Temperature " +"AI_TTS303 ","PEW 852 ","WORD ","TTS301 - Local Cip Return Temperature " +"AI_TTS399 ","PEW 840 ","WORD ","TTS399 - Electric Heat Exchanger Output Temperature " +"AI_UR29 ","DB 2122 ","FB 2121 ","Analog Input UR29 " +"AI_UR62 ","DB 2121 ","FB 2121 ","Analog Input UR62 " +"AlwaysOffBarso ","M 1.1 ","BOOL ","Always Off By Barso " +"AnalogInstrument ","UDT 82 ","UDT 82 "," " +"AnyPoint ","UDT 1 ","UDT 1 "," " +"AO_PCM306 ","PAW 824 ","WORD ","PCM306 - Gas Injection Pressure Control " +"AO_ProductRunOutAmount ","PAW 842 ","WORD ","FILLER - Product Run Out Amount [200.0 800.0 L] " +"AO_RMM301 ","PAW 800 ","WORD ","RMM301 - Water Flow Control " +"AO_RMM303 ","PAW 802 ","WORD ","RMM303 - Gas Flow Control " +"AO_RMM304 ","PAW 816 ","WORD ","RMM304 - Gas 2 Flow Control " +"AO_RMP302 ","PAW 808 ","WORD ","RMP302 - Syrup Flow Control " +"AO_RVM301 ","PAW 826 ","WORD ","RVM301 - Product Tank Pressure Valve " +"AO_RVM302 ","PAW 822 ","WORD ","RVM302 - Deaireation Tank Level / Product Tank Level Valve " +"AO_RVM304 ","PAW 810 ","WORD ","RVM304 - Product Tank pressure release " +"AO_RVM319 ","PAW 812 ","WORD ","RVM319 - Product/Water Temperature Valve " +"AO_RVN302 ","PAW 814 ","WORD ","RVN302 - Deaireation Tank Level Valve " +"AO_RVN304 ","PAW 820 ","WORD ","RVN304 - Deaireation Valve " +"AO_RVP303 ","PAW 806 ","WORD ","RVP303 - Syrup Tank Level Valve " +"AO_RVP305 ","PAW 828 ","WORD ","RVP305 - Syrup 2 Tank Level Valve " +"AO_RVS318 ","PAW 850 ","WORD ","RVS318 - Local Cip Heating Valve " +"AO_ToFillerEqPress ","PAW 840 ","WORD ","FILLER - Product Saturation Pressure [0.0 100.0 Bar/10] " +"APCO ","VAT 22 "," "," " +"AUX AMPOLLINI ","M 5.0 ","BOOL ","Always OFF - AMPOLLINI USE " +"AUX Blink_0.1S ","M 0.0 ","BOOL ","PLC Blink 0.1 Sec. 10Hz " +"AUX Blink_0.2S ","M 0.1 ","BOOL ","PLC Blink 0.2 Sec. 5Hz " +"AUX Blink_0.4S ","M 0.2 ","BOOL ","PLC Blink 0.4 Sec. 2,5Hz " +"AUX Blink_0.5S ","M 0.3 ","BOOL ","PLC Blink 0.5 Sec. 2Hz " +"AUX Blink_0.8S ","M 0.4 ","BOOL ","PLC Blink 0.8 Sec. 1,25Hz " +"AUX Blink_1.0S ","M 0.5 ","BOOL ","PLC Blink 1.0 Sec. 1Hz " +"AUX Blink_1.6S ","M 0.6 ","BOOL ","PLC Blink 1.6 Sec. 0,625Hz " +"AUX Blink_2.0S ","M 0.7 ","BOOL ","PLC Blink 2.0 Sec. 0,5Hz " +"AUX CASSARINO ","M 5.3 ","BOOL ","Always OFF - CASSARINO USE " +"AUX Clock_0.1s ","M 1.7 ","BOOL ","TIMER 0.1 sec. CLOCK " +"AUX Clock_1.0s ","M 1.5 ","BOOL ","TIMER 1.0 sec. CLOCK " +"AUX FALSE ","M 3.0 ","BOOL ","Always OFF " +"AUX FALSE_Test ","M 1.0 ","BOOL ","Always OFF for debugging purpose " +"AUX FIELD OFF ","MB 7 ","BYTE ","Always OFF - FIELD USE " +"AUX FIELD SPARE 70 ","M 7.0 ","BOOL ","Always OFF - FIELD SPARE 7.0 USE " +"AUX FIELD SPARE 71 ","M 7.1 ","BOOL ","Always OFF - FIELD SPARE 7.1 USE " +"AUX FIELD SPARE 72 ","M 7.2 ","BOOL ","Always OFF - FIELD SPARE 7.2 USE " +"AUX FIELD SPARE 73 ","M 7.3 ","BOOL ","Always OFF - FIELD SPARE 7.3 USE " +"AUX FIELD SPARE 74 ","M 7.4 ","BOOL ","Always OFF - FIELD SPARE 7.4 USE " +"AUX FIELD SPARE 75 ","M 7.5 ","BOOL ","Always OFF - FIELD SPARE 7.5 USE " +"AUX FIELD SPARE 76 ","M 7.6 ","BOOL ","Always OFF - FIELD SPARE 7.6 USE " +"AUX FIELD SPARE 77 ","M 7.7 ","BOOL ","Always OFF - FIELD SPARE 7.7 USE " +"AUX FP_Clock_0.1s ","M 2.0 ","BOOL ","FP TIMER 0.1 sec. CLOCK " +"AUX FP_Clock_1.0s ","M 2.1 ","BOOL ","FP TIMER 1.0 sec. CLOCK " +"AUX MARCHI ","M 6.0 ","BOOL ","Always OFF - MARCHI USE " +"AUX MASTER OFF ","MB 4 ","BYTE ","Always OFF - MASTER USE " +"AUX MASTER VALIDATION ","M 4.0 ","BOOL ","Always OFF - Master Validation Waiting " +"AUX MORI ","M 5.2 ","BOOL ","Always OFF - MORI USE " +"AUX PASSERA ","M 5.1 ","BOOL ","Always OFF - PASSERA USE " +"AUX PESCI ","M 6.1 ","BOOL ","Always OFF - PESCI USE " +"AUX SSAE CUSTOMIZATION ","M 4.1 ","BOOL ","Always OFF - SSAE Customization " +"AUX Start CPU ","M 1.2 ","BOOL ","Start CPU " +"AUX TEST OFF ","MB 6 ","BYTE ","Always OFF - TEST USE " +"AUX TEST SPARE 62 ","M 6.2 ","BOOL ","Always OFF - TEST SPARE 6.2 USE " +"AUX TEST SPARE 63 ","M 6.3 ","BOOL ","Always OFF - TEST SPARE 6.3 USE " +"AUX TEST SPARE 64 ","M 6.4 ","BOOL ","Always OFF - TEST SPARE 6.4 USE " +"AUX TEST SPARE 65 ","M 6.5 ","BOOL ","Always OFF - TEST SPARE 6.5 USE " +"AUX TEST SPARE 66 ","M 6.6 ","BOOL ","Always OFF - TEST SPARE 6.6 USE " +"AUX TEST SPARE 67 ","M 6.7 ","BOOL ","Always OFF - TEST SPARE 6.7 USE " +"AUX TRUE ","M 3.1 ","BOOL ","Always ON " +"AUX UTE OFF ","MB 5 ","BYTE ","Always OFF - UTE USE " +"AUX UTE SPARE 54 ","M 5.4 ","BOOL ","Always OFF - UTE SPARE 5.4 USE " +"AUX UTE SPARE 55 ","M 5.5 ","BOOL ","Always OFF - UTE SPARE 5.5 USE " +"AUX UTE SPARE 56 ","M 5.6 ","BOOL ","Always OFF - UTE SPARE 5.6 USE " +"AUX UTE SPARE 57 ","M 5.7 ","BOOL ","Always OFF - UTE SPARE 5.7 USE " +"AVS Valve Fault DB ","DB 959 ","DB 959 ","MIX - AVS Valve Fault DB " +"Baialage ","FC 1804 ","FC 1804 ","MIX - Baialage " +"Blender_Constants ","DB 972 ","DB 972 ","MIX - Blender Constants " +"Blender_Procedure Data ","DB 930 ","FB 1800 ","MIX - Blender_ProcedureCall Data " +"Blender_ProcedureCall ","FB 1800 ","FB 1800 ","MIX - Blender_Procedure Call " +"Blender_Variables ","DB 971 ","DB 971 ","MIX - Blender Variables " +"Blender_Variables_Pers ","DB 970 ","DB 970 ","MIX - Blender Variables - Persistent " +"BlenderCtrl__Main ","FC 2000 ","FC 2000 ","MIX - Blender Ctrl Main " +"BlenderCtrl_All Auto ","FC 2036 ","FC 2036 ","MIX - BlenderCtrl_All Auto " +"BlenderCtrl_CIPModeInit ","FC 2013 ","FC 2013 ","MIX - CIP Mode Init " +"BlenderCtrl_InitErrors ","FC 2001 ","FC 2001 ","MIX - Init Errors " +"BlenderCtrl_MachineInit ","FC 2003 ","FC 2003 ","MIX - BlenderCtrl_MachineInit " +"BlenderCtrl_ManualActive","FC 2037 ","FC 2037 ","MIX - ManualActive " +"BlenderCtrl_MFM Command ","FC 2033 ","FC 2033 ","MIX - MFM Command " +"BlenderCtrl_ProdModeInit","FC 2012 ","FC 2012 ","MIX - Production Mode Init " +"BlenderCtrl_ResetSPWord ","FC 2014 ","FC 2014 ","MIX - BlenderCtrl_ResetSPWord " +"BlenderCtrl_UpdatePWord ","FC 2034 ","FC 2034 ","MIX - _Blender_Ctrl_Main.Update_P_Word " +"BlenderPID__Main ","FB 1750 ","FB 1750 ","MIX - Blender PID Main " +"BlenderPID__Main_Data ","DB 950 ","FB 1750 ","MIX - Blender_PID_Main_Data " +"BlenderPID_ActualRecipe ","FB 1919 ","FB 1919 "," " +"BlenderPID_BlendingFault","FB 1753 ","FB 1753 ","MIX - PID_Blending_Fault " +"BlenderPID_FlowMeterErro","FC 1915 ","FC 1915 ","MIX - Flow_Meter_Error " +"BlenderPID_NextRecipe ","FC 1916 ","FC 1916 ","MIX - Next_Recipe " +"BlenderPID_PIDFFCalc ","FB 1752 ","FB 1752 ","MIX - PID_FF_Calc " +"BlenderPID_PIDInitParam ","FB 1755 ","FB 1755 ","MIX - PID_Init_Parameters " +"BlenderPID_PIDResInteg ","FC 1914 ","FC 1914 ","MIX - PID_Reset_Integral " +"BlenderPID_PIDSPCalc ","FB 1751 ","FB 1751 ","MIX - PID_SP_Calc " +"BlenderPIDCtrl__Loop ","FB 1729 ","FB 1729 ","MIX - PID Block Main " +"BlenderPIDCtrl_Monitor ","FB 1787 ","FB 1787 ","MIX - PID_Monitor " +"BlenderPIDCtrl_PresRelea","FC 1917 ","FC 1917 ","MIX - Pressure_Release " +"BlenderPIDCtrl_SaveInteg","FB 1788 ","FB 1788 ","MIX - Save_Integral " +"BlenderPIDCtrl_SaveValve","FC 1918 ","FC 1918 ","MIX - Save_Valve_Out " +"BlenderPIDCtrl_WriteAnOu","FC 1925 ","FC 1925 ","MIX - BlenderPIDCtrl_WriteAnOu " +"BlenderRinse ","FC 2019 ","FC 2019 ","MIX - Blender Rinse " +"BlenderRinse_Done ","FC 2042 ","FC 2042 ","MIX - Rinse Done " +"BlenderRun__Control ","FC 2015 ","FC 2015 ","MIX - Blender Run Control " +"BlenderRun_MeasFil_Data ","DB 974 ","FB 1719 ","MIX - Measure Filler Speed Data " +"BlenderRun_MeasFilSpeed ","FB 1719 ","FB 1719 ","MIX - Measure Filler Speed " +"BlenderRun_ProdTime ","FC 2040 ","FC 2040 ","MIX - Production Time " +"BlenderRun_SelectConstan","FC 2038 ","FC 2038 ","MIX - Select Blender Constant " +"BlenderRun_Stopping ","FC 2041 ","FC 2041 ","MIX - Stopping Blender " +"BlendFill StartUp ","FB 1814 ","FB 1814 ","MIX - BlendFill StartUp " +"BlendFill StartUp_Seq ","FC 1814 ","FC 1814 ","MIX - Blend Fill System StartUp Sequencer " +"BLKMOV ","SFC 20 ","SFC 20 ","MIX - Copy Variables " +"Block_compare ","FC 61 ","FC 61 "," " +"Block_move ","FC 60 ","FC 60 ","Move blocks from DB to DB " +"BrixTracking ","FB 1801 ","FB 1801 ","MIX - Brix Tracking " +"BrixTracking_Data ","DB 975 ","FB 1801 ","MIX - Brix Tracking Data " +"BrixTracking_ProdSamples","FB 1705 ","FB 1705 ","MIX - Prod Samples " +"BrixTracking_SampleTime ","FB 1706 ","FB 1706 ","MIX - Sample Time Calc " +"Buffer_Tank_Flooding_DB ","DB 101 ","DB 101 ","Buffer Tank Flooding DB " +"Buffer_Tank_Flushing ","FC 101 ","FC 101 ","Product Tank Flushing " +"CarboWaterLine ","FB 1802 ","FB 1802 ","MIX - CarboWaterLine " +"CarboWaterLine_Seq ","FC 1802 ","FC 1802 ","MIX - CarboWaterLine Sequencer " +"Cetrifugal_Head ","FC 1842 ","FC 1842 ","MIX - Cetrifugal_Head " +"CIP CVQ ","FC 1905 ","FC 1905 ","MIX - CIP_CVQ " +"CIP Prog Control ","FC 82 ","FC 82 ","MIX - CIP program condition control " +"CIP_Link_Type ","UDT 900 ","UDT 900 ","MIX - CIP_Link_Type " +"CIP_Program_Variables ","DB 963 ","DB 963 ","MIX - CIP Program Variables " +"CIP_Simple_Type ","UDT 901 ","UDT 901 ","MIX - CIP_Simple_Type " +"CIP_SimpleProgr_Init ","FC 1936 ","FC 1936 ","MIX - CIP_SimpleProgr_Initialize " +"CIP_Step_Type ","UDT 903 ","UDT 903 ","MIX - CIP_Step_Type " +"CIP_Step_Type_New ","UDT 899 ","UDT 899 ","MIX - CIP_Step_Type " +"CIP_WaitEvent_Type ","UDT 902 ","UDT 902 ","MIX - CIP_WaitEvent_Type " +"CIPLocal ","FC 1933 ","FC 1933 ","MIX - CIP Local " +"CIPLocal_DSCtrl ","FC 1940 ","FC 1940 ","MIX - ICS " +"CIPLocal_ExecSimpleCIP ","FC 1935 ","FC 1935 ","MIX - Execute CIP program " +"CIPLocal_ExecStep ","FC 1941 ","FC 1941 ","MIX - " +"CIPLocal_ManualVolDosing","FC 1943 ","FC 1943 ","MIX - CIP Local Manual Volume Dosing " +"CIPLocal_ProgInizialize ","FC 1937 ","FC 1937 ","MIX - CIP program inizialization " +"CIPLocal_ProgStepDown ","FC 1938 ","FC 1938 ","MIX - " +"CIPLocal_ProgStepUp ","FC 1939 ","FC 1939 ","MIX - " +"CIPLocal_SyrupPipeCIP ","FC 1944 ","FC 1944 ","MIX - CIP Local Syrup Pipe CIP " +"CIPLocal_WaitEvent_Ctrl ","FC 1934 ","FC 1934 ","MIX - CIPLocal_WaitEvent_Ctrl " +"CIPLocal_WaterPipeCIP ","FC 1942 ","FC 1942 ","MIX - CIP Local Water Pipe CIP " +"CIPMain ","FC 1930 ","FC 1930 ","MIX - " +"CIPMain_Flood ","FC 1931 ","FC 1931 ","MIX - " +"CIPMain_Total Drain ","FC 1932 ","FC 1932 ","MIX - " +"CIPRecipe#01 ","DB 501 ","DB 501 ","CIPRecipe#01 " +"CIPRecipe#02 ","DB 502 ","DB 502 ","CIPRecipe#02 " +"CIPRecipe#03 ","DB 503 ","DB 503 ","CIPRecipe#03 " +"CIPRecipe#04 ","DB 504 ","DB 504 ","CIPRecipe#04 " +"CIPRecipe#05 ","DB 505 ","DB 505 ","CIPRecipe#05 " +"CIPRecipe#06 ","DB 506 ","DB 506 ","CIPRecipe#06 " +"CIPRecipe#07 ","DB 507 ","DB 507 ","CIPRecipe#07 " +"CIPRecipe#08 ","DB 508 ","DB 508 ","CIPRecipe#08 " +"CIPRecipe#09 ","DB 509 ","DB 509 ","CIPRecipe#09 " +"CIPRecipe#10 ","DB 510 ","DB 510 ","CIPRecipe#10 " +"CIPRecipe#11 ","DB 511 ","DB 511 ","CIPRecipe#11 " +"CIPRecipe#12 ","DB 512 ","DB 512 ","CIPRecipe#12 " +"CIPRecipe#13 ","DB 513 ","DB 513 ","CIPRecipe#13 " +"CIPRecipe#14 ","DB 514 ","DB 514 ","CIPRecipe#14 " +"CIPRecipe#15 ","DB 515 ","DB 515 ","CIPRecipe#15 " +"CIPRecipe#16 ","DB 516 ","DB 516 ","CIPRecipe#16 " +"CIPRecipe#17 ","DB 517 ","DB 517 ","CIPRecipe#17 " +"CIPRecipe#18 ","DB 518 ","DB 518 ","CIPRecipe#18 " +"CIPRecipe#19 ","DB 519 ","DB 519 ","CIPRecipe#19 " +"CIPRecipe#20 ","DB 520 ","DB 520 ","CIPRecipe#20 " +"CIPRecipe#21 ","DB 521 ","DB 521 ","CIPRecipe#21 " +"CIPRecipe#22 ","DB 522 ","DB 522 ","CIPRecipe#22 " +"CIPRecipe#23 ","DB 523 ","DB 523 ","CIPRecipe#23 " +"CIPRecipe#24 ","DB 524 ","DB 524 ","CIPRecipe#24 " +"CIPRecipe#25 ","DB 525 ","DB 525 ","CIPRecipe#25 " +"CIPRecipe#26 ","DB 526 ","DB 526 ","CIPRecipe#26 " +"CIPRecipe#27 ","DB 527 ","DB 527 ","CIPRecipe#27 " +"CIPRecipe#28 ","DB 528 ","DB 528 ","CIPRecipe#28 " +"CIPRecipe#29 ","DB 529 ","DB 529 ","CIPRecipe#29 " +"CIPRecipe#30 ","DB 530 ","DB 530 ","CIPRecipe#30 " +"CIPRepComment ","MW 602 ","INT ","CIP Report Comment " +"CIPRepFaultPresent ","M 601.4 ","BOOL ","CIP Report Fault Present " +"CIPReportDB ","DB 600 ","DB 600 "," " +"CIPReportManager ","FC 600 ","FC 600 "," " +"CIPRepStarted ","M 600.0 ","BOOL ","CIP Report Started " +"CIPSimple_Drain ","DB 543 ","DB 543 ","MIX - ICS " +"CIPSimple_Empty ","DB 540 ","DB 540 ","MIX - ICS " +"CIPSimple_Flood ","DB 544 ","DB 544 ","MIX - ICS " +"CIPSimple_Recirculation ","DB 542 ","DB 542 ","MIX - ICS " +"CIPSimple_Recover ","DB 547 ","DB 547 ","MIX - ICS " +"CIPSimple_Rinse ","DB 541 ","DB 541 ","MIX - ICS " +"CIPSimple_RinseCO2 ","DB 545 ","DB 545 ","MIX - ICS " +"CIPSimple_Start ","DB 546 ","DB 546 ","MIX - ICS " +"CLK_0.1S ","M 1931.0 ","BOOL ","MIX - CLK_0.1S " +"CLK_0.1S_SUPPORT ","M 1930.0 ","BOOL ","MIX - CLK_0.1S_SUPPORT " +"CLK_0.2S ","M 1931.1 ","BOOL ","MIX - CLK_0.2S " +"CLK_0.2S_SUPPORT ","M 1930.1 ","BOOL ","MIX - CLK_0.2S_SUPPORT " +"CLK_0.4S ","M 1931.2 ","BOOL ","MIX - CLK_0.4S " +"CLK_0.4S_SUPPORT ","M 1930.2 ","BOOL ","MIX - CLK_0.4S_SUPPORT " +"CLK_0.5S ","M 1931.3 ","BOOL ","MIX - CLK_0.5S " +"CLK_0.5S_SUPPORT ","M 1930.3 ","BOOL ","MIX - CLK_0.5S_SUPPORT " +"CLK_0.8S ","M 1931.4 ","BOOL ","MIX - CLK_0.8S " +"CLK_0.8S_SUPPORT ","M 1930.4 ","BOOL ","MIX - CLK_0.8S SUPPORT " +"CLK_1.0S ","M 1931.5 ","BOOL ","MIX - CLK_1.0S " +"CLK_1.0S_SUPPORT ","M 1930.5 ","BOOL ","MIX - CLK_1.0S_SUPPORT " +"CLK_1.6S ","M 1931.6 ","BOOL ","MIX - CLK_1.6S " +"CLK_1.6S_SUPPORT ","M 1930.6 ","BOOL ","MIX - CLK_1.6S_SUPPORT " +"CLK_2.0S ","M 1931.7 ","BOOL ","MIX - CLK_2.0S " +"CLK_2.0S_SUPPORT ","M 1930.7 ","BOOL ","MIX - CLK_2.0S_SUPPORT " +"Clock Signal ","FC 1860 ","FC 1860 ","MIX - Clock Generation " +"CO2 Solubility ","FC 1911 ","FC 1911 ","MIX - CO2_Solubility " +"CO2EqPress ","FC 1908 ","FC 1908 ","MIX - CO2_Eq_Press " +"CO2InjPressure ","FC 1923 ","FC 1923 ","MIX - CO2_Injection_Pressure " +"CO2Tracking ","FB 1818 ","FB 1818 ","MIX - CO2Tracking " +"CO2Tracking_Data ","DB 976 ","FB 1818 ","MIX - CO2 Tracking Data " +"CO2Tracking_ProdSamples ","FB 1709 ","FB 1709 ","MIX - CO2Tracking_ProdSamples " +"CO2Tracking_SampleTime ","FB 1710 ","FB 1710 ","MIX - CO2Tracking_SampleTime " +"Co2TrackingLatch ","M 1906.7 ","BOOL ","MIX - FP******* " +"Co2TrackingReq ","M 1906.6 ","BOOL ","MIX - FP****** " +"COMM_FLT ","OB 87 ","OB 87 ","Communication Fault " +"COMPLETE RESTART ","OB 100 ","OB 100 ","Complete Restart " +"CONCAT ","FC 2 ","FC 2 ","Concatenate String " +"CPU_DP Global Diag ","FC 14 ","FC 14 ","CPU and Profibus detailed diagnostic " +"CTRLCoolingSystem ","FC 1700 ","FC 1700 "," " +"CTS301_Conductiv_State ","PEB 1114 ","BYTE ","MIX - Profibus Variables " +"CTS301_Conductivity ","PED 1110 ","REAL ","MIX - Profibus Variables " +"CTS301_Temperat_State ","PEB 1119 ","BYTE ","MIX - Profibus Variables " +"CTS301_Temperature ","PED 1115 ","REAL ","MIX - Profibus Variables " +"CTS302_Conductiv_State ","PEB 1124 ","BYTE ","MIX - Profibus Variables " +"CTS302_Conductivity ","PED 1120 ","REAL ","MIX - Profibus Variables " +"CTS302_Temperat_State ","PEB 1129 ","BYTE ","MIX - Profibus Variables " +"CTS302_Temperature ","PED 1125 ","REAL ","MIX - Profibus Variables " +"CTUD ","SFB 2 ","SFB 2 ","MIX - Count Up / Down " +"CVQ_1p7_8_Perc ","FC 1913 ","FC 1913 ","MIX - CVQ_1p7_8_Perc " +"CYC_INT5 ","OB 35 ","OB 35 ","Cyclic Interrupt 5 " +"D_ACT_DP ","SFC 12 ","SFC 12 ","MIX - Deactivating and Activating DP Slaves " +"Danfoss Diag ","UDT 88 ","UDT 88 "," " +"DAR_Logic ","FB 2124 ","FB 2124 "," " +"DAR_Logic_DB ","DB 2124 ","FB 2124 "," " +"DeairCO2TempComp ","FC 1909 ","FC 1909 ","MIX - Deair_CO2_Temp_Comp " +"DeaireationValve ","FC 1924 ","FC 1924 ","MIX - " +"Deaireator StartUp ","FB 1803 ","FB 1803 ","MIX - Deaireator StartUp " +"Deaireator StartUp_Seq ","FC 1803 ","FC 1803 ","MIX - Deaireator StartUp Sequencer " +"Delay ","FB 1708 ","FB 1708 ","MIX - Delay " +"DELETE ","FC 4 ","FC 4 ","Delete String " +"DeltaP ","FC 1921 ","FC 1921 ","MIX - Delta_P " +"DETAIL_DP_DIAG ","FB 125 ","FB 125 ","Profibus Network detailed Diagnostic " +"DETAIL_DP_DIAG_i ","DB 14 ","FB 125 ","Profibus Network detailed Diagnostic Data " +"Device ","UDT 81 ","UDT 81 "," " +"DI_Air_InletPress_OK ","E 30.0 ","BOOL ","Air Pressure Switch " +"DI_AuxVoltage_On ","E 5.1 ","BOOL ","Electrical Panel Restored " +"DI_AVM308_Close ","E 110.2 ","BOOL ","MIX - Product Tank Inlet - Close FBK " +"DI_AVM308_Open ","E 100.2 ","BOOL ","MIX - Product Tank Inlet - Open FBK " +"DI_AVM309_Close ","E 110.7 ","BOOL ","MIX - Product Tank Loading - Close FBK " +"DI_AVM309_Open ","E 100.7 ","BOOL ","MIX - Product Tank Loading - Open FBK " +"DI_AVM311_Close ","E 110.6 ","BOOL ","MIX - Product Tank Blow Off - Close FBK " +"DI_AVM311_Open ","E 100.6 ","BOOL ","MIX - Product Tank Blow Off - Open FBK " +"DI_AVM313_Close ","E 111.0 ","BOOL ","MIX - Product Recirculation - Close FBK " +"DI_AVM313_Open ","E 101.0 ","BOOL ","MIX - Product Recirculation - Open FBK " +"DI_AVM315_Close ","E 111.1 ","BOOL ","MIX - Product Tank Drain - Close FBK " +"DI_AVM315_Open ","E 101.1 ","BOOL ","MIX - Product Tank Drain - Open FBK " +"DI_AVM317_Close ","E 111.2 ","BOOL ","MIX - Product Tank Spray Ball - Close FBK " +"DI_AVM317_Open ","E 101.2 ","BOOL ","MIX - Product Tank Spray Ball - Open FBK " +"DI_AVM321_Close ","E 111.3 ","BOOL ","MIX - Product Tank Over Fill - Close FBK " +"DI_AVM321_Open ","E 101.3 ","BOOL ","MIX - Product Tank Over Fill - Open FBK " +"DI_AVM322_Close ","E 111.4 ","BOOL ","MIX - Water Pump PPN301 Priming - Close FBK " +"DI_AVM322_Open ","E 101.4 ","BOOL ","MIX - Water Pump PPN301 Priming - Open FBK " +"DI_AVM324_Close ","E 111.5 ","BOOL ","MIX - Water Chiller Pipe Drain - Close FBK " +"DI_AVM324_Open ","E 101.5 ","BOOL ","MIX - Water Chiller Pipe Drain - Open FBK " +"DI_AVM326_Close ","E 111.6 ","BOOL ","MIX - CO2 Injection Shut Off - Close FBK " +"DI_AVM326_Open ","E 101.6 ","BOOL ","MIX - CO2 Injection Shut Off - Open FBK " +"DI_AVM327_Close ","E 312.4 ","BOOL ","MIX - Blendfill Still Water By-Pass Mixproof 1 - Close FBK " +"DI_AVM327_Open ","E 302.4 ","BOOL ","MIX - Blendfill Still Water By-Pass Mixproof 1 - Open FBK " +"DI_AVM328_Close ","E 301.4 ","BOOL ","MIX - Blendfill Still Water By-Pass Mixproof 2 - Close FBK " +"DI_AVM328_Open ","E 201.4 ","BOOL ","MIX - Blendfill Still Water By-Pass Mixproof 2 - Open FBK " +"DI_AVM329_Close ","E 301.6 ","BOOL ","MIX - Blendfill Still Water By-Pass Mixproof 3 - Close FBK " +"DI_AVM329_Open ","E 201.6 ","BOOL ","MIX - Blendfill Still Water By-Pass Mixproof 3 - Open FBK " +"DI_AVM331_Close ","E 301.7 ","BOOL ","MIX - No Blendfill Still Water By-Pass to Mixer - Close FBK " +"DI_AVM331_Open ","E 201.7 ","BOOL ","MIX - No Blendfill Still Water By-Pass to Mixer - Open FBK " +"DI_AVM341_Close ","E 111.7 ","BOOL ","MIX - CO2 Inlet - Close FBK " +"DI_AVM341_Open ","E 101.7 ","BOOL ","MIX - CO2 Inlet - Open FBK " +"DI_AVM342_Close ","E 112.0 ","BOOL ","MIX - N2 Inlet - Close FBK " +"DI_AVM342_Open ","E 102.0 ","BOOL ","MIX - N2 Inlet - Open FBK " +"DI_AVM345_Close ","E 112.1 ","BOOL ","MIX - Product Chiller Inlet Drain - Close FBK " +"DI_AVM345_Open ","E 102.1 ","BOOL ","MIX - Product Chiller Inlet Drain - Open FBK " +"DI_AVM346_Close ","E 112.2 ","BOOL ","MIX - Filler CO2 - Close FBK " +"DI_AVM346_Open ","E 102.2 ","BOOL ","MIX - Filler CO2 - Open FBK " +"DI_AVM348_Close ","E 112.4 ","BOOL ","MIX - Product Chiller Outlet Drain - Close FBK " +"DI_AVM348_Open ","E 102.4 ","BOOL ","MIX - Product Chiller Outlet Drain - Open FBK " +"DI_AVM349_Close ","E 312.0 ","BOOL ","MIX - Product Chiller Outlet Drain - Close FBK " +"DI_AVM349_Open ","E 302.0 ","BOOL ","MIX - Product Chiller Outlet Drain - Open FBK " +"DI_AVM362_Close ","E 112.3 ","BOOL ","MIX - Product Outlet - Close FBK " +"DI_AVM362_Open ","E 102.3 ","BOOL ","MIX - Product Outlet - Open FBK " +"DI_AVM378_Close ","E 301.2 ","BOOL ","MIX - Product Outlet Switch - Close FBK " +"DI_AVM378_Open ","E 302.5 ","BOOL ","MIX - Product Outlet Switch - Open FBK " +"DI_AVM381_Close ","E 112.5 ","BOOL ","MIX - Product Recirculation With Chiller - Close FBK " +"DI_AVM381_Open ","E 102.5 ","BOOL ","MIX - Product Recirculation With Chiller - Open FBK " +"DI_AVM385_Close ","E 312.5 ","BOOL ","MIX - Still Water By-Pass Drain Inlet Water - Close FBK " +"DI_AVM385_Open ","E 212.5 ","BOOL ","MIX - Still Water By-Pass Drain Inlet Water - Open FBK " +"DI_AVM386_Close ","E 122.5 ","BOOL ","MIX - Still Water By-Pass Drain Outlet Water - Close FBK " +"DI_AVM386_Open ","E 222.5 ","BOOL ","MIX - Still Water By-Pass Drain Outlet Water - Open FBK " +"DI_AVM387_Close ","E 312.1 ","BOOL ","MIX - Blendfill Still Water By-Pass Product Interception - Close FBK " +"DI_AVM387_Open ","E 302.1 ","BOOL ","MIX - Blendfill Still Water By-Pass Product Interception - Open FBK " +"DI_AVM388_Close ","E 112.6 ","BOOL ","MIX - Drain Sanification Pipe - Close FBK " +"DI_AVM388_Open ","E 102.6 ","BOOL ","MIX - Drain Sanification Pipe - Open FBK " +"DI_AVM389_Close ","E 112.7 ","BOOL ","MIX - Sanification to Spray Balls - Close FBK " +"DI_AVM389_Open ","E 102.7 ","BOOL ","MIX - Sanification to Spray Balls - Open FBK " +"DI_AVM390_Close ","E 313.0 ","BOOL ","MIX - Chiller Glycol Return interception - Close FBK " +"DI_AVM390_Open ","E 213.0 ","BOOL ","MIX - Chiller Glycol Return interception - Open FBK " +"DI_AVM391_Close ","E 143.0 ","BOOL ","MIX - Chiller Glycol Expansion - Close FBK " +"DI_AVM391_Open ","E 243.0 ","BOOL ","MIX - Chiller Glycol Expansion - Open FBK " +"DI_AVM396_Close ","E 143.1 ","BOOL ","MIX - Tank Pressurization with Steril Air - Close FBK " +"DI_AVM396_Open ","E 243.1 ","BOOL ","MIX - Tank Pressurization with Steril Air - Open FBK " +"DI_AVM397_Close ","E 123.1 ","BOOL ","MIX - CO2 Buffer Tank Intercept - Close FBK " +"DI_AVM397_Open ","E 223.1 ","BOOL ","MIX - CO2 Buffer Tank Intercept - Open FBK " +"DI_AVM398_Close ","E 313.1 ","BOOL ","MIX - Blendfill Steril Air Inlet - Close FBK " +"DI_AVM398_Open ","E 213.1 ","BOOL ","MIX - Blendfill Steril Air Inlet - Open FBK " +"DI_AVN301_Close ","E 115.4 ","BOOL ","MIX - Deaireator Water Inlet #1 - Close FBK " +"DI_AVN301_Open ","E 105.4 ","BOOL ","MIX - Deaireator Water Inlet #1 - Open FBK " +"DI_AVN302_Close ","E 115.5 ","BOOL ","MIX - Deaireator Water Inlet #2 - Close FBK " +"DI_AVN302_Open ","E 105.5 ","BOOL ","MIX - Deaireator Water Inlet #2 - Open FBK " +"DI_AVN303_Close ","E 115.6 ","BOOL ","MIX - Deaireator Water Inlet #3 - Close FBK " +"DI_AVN303_Open ","E 105.6 ","BOOL ","MIX - Deaireator Water Inlet #3 - Open FBK " +"DI_AVN305_Close ","E 310.5 ","BOOL ","MIX - Premixed Product Inlet - Close FBK " +"DI_AVN305_Open ","E 210.5 ","BOOL ","MIX - Premixed Product Inlet - Open FBK " +"DI_AVN306_Close ","E 310.6 ","BOOL ","MIX - Premixed Product Priming - Close FBK " +"DI_AVN306_Open ","E 210.6 ","BOOL ","MIX - Premixed Product Priming - Open FBK " +"DI_AVN314_Close ","E 110.0 ","BOOL ","MIX - Deaireator Drain - Close FBK " +"DI_AVN314_Open ","E 100.0 ","BOOL ","MIX - Deaireator Drain - Open FBK " +"DI_AVN318_Close ","E 110.1 ","BOOL ","MIX - Deaireator Over Fill - Close FBK " +"DI_AVN318_Open ","E 100.1 ","BOOL ","MIX - Deaireator Over Fill - Open FBK " +"DI_AVN323_Close ","E 113.5 ","BOOL ","MIX - Water Pipe Drain - Close FBK " +"DI_AVN323_Open ","E 103.5 ","BOOL ","MIX - Water Pipe Drain - Open FBK " +"DI_AVN324_Close ","E 113.6 ","BOOL ","MIX - Deaireator 1st Stage Drain - Close FBK " +"DI_AVN324_Open ","E 103.6 ","BOOL ","MIX - Deaireator 1st Stage Drain - Open FBK " +"DI_AVN327_Close ","E 110.3 ","BOOL ","MIX - Deaireator Tank Spray Ball - Close FBK " +"DI_AVN327_Open ","E 100.3 ","BOOL ","MIX - Deaireator Tank Spray Ball - Open FBK " +"DI_AVN328_Close ","E 110.4 ","BOOL ","MIX - Deaireator Strip CO2 Injection - Close FBK " +"DI_AVN328_Open ","E 100.4 ","BOOL ","MIX - Deaireator Strip CO2 Injection - Open FBK " +"DI_AVN370_Close ","E 113.7 ","BOOL ","AVN370 - Close FBK " +"DI_AVN370_Open ","E 103.7 ","BOOL ","AVN370 - Open FBK " +"DI_AVN371_Close ","E 114.0 ","BOOL ","AVN371- Close FBK " +"DI_AVN371_Open ","E 104.0 ","BOOL ","AVN371- Open FBK " +"DI_AVN373_Close ","E 304.2 ","BOOL ","MIX - Deaireator Recirculation Pipe Drain - Close FBK " +"DI_AVN373_Open ","E 204.2 ","BOOL ","MIX - Deaireator Recirculation Pipe Drain - Open FBK " +"DI_AVN390_Close ","E 115.2 ","BOOL ","AVN390 " +"DI_AVN390_Open ","E 105.2 ","BOOL ","AVN390 " +"DI_AVN395_Close ","E 110.5 ","BOOL ","MIX - Vacuum Pump Deaireator Intercept - Close FBK " +"DI_AVN395_Open ","E 100.5 ","BOOL ","MIX - Vacuum Pump Deaireator Intercept - Open FBK " +"DI_AVP315_Close ","E 114.1 ","BOOL ","MIX - Syrup Injection - Close FBK " +"DI_AVP315_Open ","E 104.1 ","BOOL ","MIX - Syrup Injection - Open FBK " +"DI_AVP316_Close ","E 113.0 ","BOOL ","MIX - Syrup Tank Drain - Close FBK " +"DI_AVP316_Open ","E 103.0 ","BOOL ","MIX - Syrup Tank Drain - Open FBK " +"DI_AVP317_Close ","E 113.1 ","BOOL ","MIX - Syrup Cip Inlet - Close FBK " +"DI_AVP317_Open ","E 103.1 ","BOOL ","MIX - Syrup Cip Inlet - Open FBK " +"DI_AVP320_Close ","E 114.2 ","BOOL ","AVP320- Close FBK " +"DI_AVP320_Open ","E 104.2 ","BOOL ","AVP320- Open FBK " +"DI_AVP324_Close ","E 113.2 ","BOOL ","MIX - Syrup Recirculation - Close FBK " +"DI_AVP324_Open ","E 103.2 ","BOOL ","MIX - Syrup Recirculation - Open FBK " +"DI_AVP344_Close ","E 113.3 ","BOOL ","MIX - Syrup Line Drain - Close FBK " +"DI_AVP344_Open ","E 103.3 ","BOOL ","MIX - Syrup Line Drain - Open FBK " +"DI_AVP361_Close ","E 113.4 ","BOOL ","MIX - Syrup Tank Spray Ball - Close FBK " +"DI_AVP361_Open ","E 103.4 ","BOOL ","MIX - Syrup Tank Spray Ball - Open FBK " +"DI_AVP363_Close ","E 314.6 ","BOOL ","MIX - Syrup Line In H2O - Close FBK " +"DI_AVP363_Open ","E 214.6 ","BOOL ","MIX - Syrup Line In H2O - Open FBK " +"DI_AVP364_Close ","E 303.5 ","BOOL ","MIX - Syrup Line In Syrup 1 - Close FBK " +"DI_AVP364_Open ","E 203.5 ","BOOL ","MIX - Syrup Line In Syrup 1 - Open FBK " +"DI_AVP365_Close ","E 314.7 ","BOOL ","MIX - Syrup Line In Syrup 2 - Close FBK " +"DI_AVP365_Open ","E 214.7 ","BOOL ","MIX - Syrup Line In Syrup 2 - Open FBK " +"DI_AVP388_Close ","E 125.0 ","BOOL ","MIX - Syrup Cip Inlet with Local Cip - Close FBK " +"DI_AVP388_Open ","E 108.0 ","BOOL ","MIX - Syrup Cip Inlet with Local Cip - Open FBK " +"DI_AVS301_Close ","E 145.0 ","BOOL ","MIX - CIP Chemical Dosing - Close FBK " +"DI_AVS301_Open ","E 245.0 ","BOOL ","MIX - CIP Chemical Dosing - Open FBK " +"DI_AVS302_Close ","E 350.1 ","BOOL ","MIX - CIP Chemical Injection - Close FBK " +"DI_AVS302_Open ","E 205.1 ","BOOL ","MIX - CIP Chemical Injection - Open FBK " +"DI_AVS341_Close ","E 114.3 ","BOOL ","MIX - CIP Loop Return - Close FBK " +"DI_AVS341_Open ","E 104.3 ","BOOL ","MIX - CIP Loop Return - Open FBK " +"DI_AVS342_Close ","E 125.2 ","BOOL ","MIX - CIP Loop Return Drain - Close FBK " +"DI_AVS342_Open ","E 108.2 ","BOOL ","MIX - CIP Loop Return Drain - Open FBK " +"DI_AVS343_Close ","E 114.4 ","BOOL ","MIX - CIP Water Inlet Cleaning Return - Close FBK " +"DI_AVS343_Open ","E 104.4 ","BOOL ","MIX - CIP Water Inlet Cleaning Return - Open FBK " +"DI_AVS344_Close ","E 114.5 ","BOOL ","MIX - CIP Syrup Inlet Cleaning Return - Close FBK " +"DI_AVS344_Open ","E 104.5 ","BOOL ","MIX - CIP Syrup Inlet Cleaning Return - Open FBK " +"DI_AVS345_Close ","E 114.6 ","BOOL ","MIX - CIP Process Water Inlet Mixproof 1 - Close FBK " +"DI_AVS345_Open ","E 104.6 ","BOOL ","MIX - CIP Process Water Inlet Mixproof 1 - Open FBK " +"DI_AVS346_Close ","E 114.7 ","BOOL ","MIX - CIP Process Water Inlet Mixproof 2 - Close FBK " +"DI_AVS346_Open ","E 104.7 ","BOOL ","MIX - CIP Process Water Inlet Mixproof 2 - Open FBK " +"DI_AVS347_Close ","E 115.0 ","BOOL ","MIX - CIP Syrup Inlet Mixproof 1 - Close FBK " +"DI_AVS347_Open ","E 105.0 ","BOOL ","MIX - CIP Syrup Inlet Mixproof 1 - Open FBK " +"DI_AVS348_Close ","E 115.1 ","BOOL ","MIX - CIP Syrup Inlet Mixproof 2 - Close FBK " +"DI_AVS348_Open ","E 105.1 ","BOOL ","MIX - CIP Syrup Inlet Mixproof 2 - Open FBK " +"DI_CIP_CleaningCompleted","E 140.1 ","BOOL ","CIP - Cip Cleaning Completed " +"DI_CIP_CleaningFault ","E 140.2 ","BOOL ","CIP - Cip in Fault " +"DI_CIP_DrainRequest ","E 141.2 ","BOOL ","CIP - Cip Drain Request " +"DI_CIP_HotWaterSending ","E 60.7 ","BOOL ","CIP - Hot Water Sending " +"DI_CIP_HotWaterStop ","E 61.0 ","BOOL ","MIX - Hot Water Stop " +"DI_CIP_ProductSending ","E 140.3 ","BOOL ","CIP - Cip Product Sending " +"DI_CIP_RecoverRequest ","E 141.1 ","BOOL ","CIP - Cip Recover Request " +"DI_CIP_Running ","E 141.0 ","BOOL ","CIP - Cip Running " +"DI_CIP_TankFilling ","E 60.5 ","BOOL ","CIP - Flooding Request " +"DI_CO2_InletPress_OK ","E 33.2 ","BOOL "," - CO2 Inlet Pressure Switch " +"DI_EHS301_Contactor ","E 14.0 ","BOOL ","EHS301 - Heat Resistance #01 Feedback " +"DI_EHS301_Ovrld ","E 13.0 ","BOOL ","EHS301 - Heat Resistance #01 Overload " +"DI_EHS302_Contactor ","E 14.1 ","BOOL ","EHS302 - Heat Resistance #02 Feedback " +"DI_EHS302_Ovrld ","E 13.1 ","BOOL ","EHS302 - Heat Resistance #03 Overload " +"DI_EHS303_Contactor ","E 14.2 ","BOOL ","EHS303 - Heat Resistance #03 Feedback " +"DI_EHS303_Ovrld ","E 13.2 ","BOOL ","EHS303 - Heat Resistance #03 Overload " +"DI_EHS304_Contactor ","E 14.3 ","BOOL ","EHS304 - Heat Resistance #04 Feedback " +"DI_EHS304_Ovrld ","E 13.3 ","BOOL ","EHS304 - Heat Resistance #04 Overload " +"DI_EHS305_Contactor ","E 14.4 ","BOOL ","EHS305 - Heat Resistance #05 Feedback " +"DI_EHS305_Ovrld ","E 13.4 ","BOOL ","EHS305 - Heat Resistance #05 Overload " +"DI_EHS306_Contactor ","E 14.5 ","BOOL ","EHS306 - Heat Resistance #06 Feedback " +"DI_EHS306_Ovrld ","E 13.5 ","BOOL ","EHS306 - Heat Resistance #06 Overload " +"DI_Emergency_Pressed ","E 5.2 ","BOOL ","Electrical Panel Emergency Button " +"DI_Flr_CIP_CleaningAlarm","E 62.1 ","BOOL ","FILLER - Alarm " +"DI_Flr_CIP_CleaningReq ","E 62.0 ","BOOL ","FILLER - Ready for Cip " +"DI_Flr_CIP_DrainComplete","E 62.4 ","BOOL ","FILLER - Cip Drain Complete " +"DI_Flr_CIP_FloodingEnd ","E 62.5 ","BOOL ","FILLER - Flooding End " +"DI_Flr_CIP_RecoverReq ","E 62.3 ","BOOL ","FILLER - Recover Request " +"DI_Flr_CIP_RinseComplete","E 63.1 ","BOOL ","FILLER - Rinse Cycle End " +"DI_Flr_CIP_RinseMode ","E 63.0 ","BOOL ","FILLER - Rinse Mode " +"DI_Flr_CIP_SolutionRetun","E 62.2 ","BOOL ","FILLER - Cip Solution Return " +"DI_Flr_FastChangeLastCon","E 64.6 ","BOOL ","FILLER - Fast Change Over Last Container " +"DI_Flr_PROD_Request ","E 64.1 ","BOOL ","FILLER - Product Request " +"DI_Flr_PROD_Selected ","E 64.0 ","BOOL ","FILLER - Ready for Production " +"DI_FSS301 ","E 30.7 ","BOOL ","FSS301 - Local Cip Return Flow Switch " +"DI_FTG301_Pulse ","E 40.3 ","BOOL ","MIX - Compressed Air Pulse " +"DI_FTG302_Pulse ","E 40.4 ","BOOL ","MIX - CO2 Pulse " +"DI_FTM304_Pulse ","E 30.1 ","BOOL ","FTM304_Pulse " +"DI_FTN390_Pulse ","E 40.2 ","BOOL ","MIX - Water/CIP Pulse " +"DI_GSM307 ","E 38.0 ","BOOL ","GSM307 " +"DI_GSM308 ","E 38.1 ","BOOL ","GSM308 " +"DI_GSM309 ","E 38.2 ","BOOL ","GSM309 " +"DI_GSM310 ","E 38.3 ","BOOL ","GSM310 " +"DI_GSM322 ","E 38.4 ","BOOL ","GSM322 " +"DI_GSM323 ","E 38.5 ","BOOL ","GSM323 " +"DI_HVP301_Sensor ","E 300.3 ","BOOL ","GCP301 - Manual Syrup Valve Closed (NO) " +"DI_LSM302L ","E 30.2 ","BOOL ","LSM302_L - Product Tank Minimun Level " +"DI_LSN301L ","E 30.3 ","BOOL ","LSN301_L - Deaireator Tank Minimun Level " +"DI_LSN305 ","E 32.0 ","BOOL ","LSN305 - Vacuum Pump Circuit Minimum Level " +"DI_MaxPPN304 ","E 320.2 ","BOOL "," - PPN304 Water Level High " +"DI_MaxTempAlarm ","E 5.0 ","BOOL ","Electrical Cabinet High Temperature " +"DI_MinPPN304 ","E 315.0 ","BOOL "," - PPN304 Water Level low " +"DI_PhMeter_CIP_Mode ","E 32.2 ","BOOL ","PHMeter CIP Mode " +"DI_PhMeter_Prod_Mode ","E 32.3 ","BOOL ","PHMeter Prod Mode " +"DI_PowerMeter_KVARh ","E 34.2 ","BOOL ","Power Meter KVARh " +"DI_PowerMeter_KWh ","E 34.3 ","BOOL ","Power Meter KWh " +"DI_PowerMeter_Ovrld ","E 300.6 ","BOOL ","Power Meter - Overload " +"DI_PPM303_Contactor ","E 11.2 ","BOOL ","PPM303 - Product Pump Feedback " +"DI_PPM303_Ovrld ","E 10.2 ","BOOL ","PPM303 - Product Pump Overload " +"DI_PPM303_SafetySwitch ","E 7.2 ","BOOL ","PPM303 - Product Pump Safety Switch " +"DI_PPM306_Contactor ","E 11.3 ","BOOL ","PPM306 - Recirculating Pump Feedback " +"DI_PPM306_Ovrld ","E 10.3 ","BOOL ","PPM306 - Recirculating Pump Overload " +"DI_PPM306_SafetySwitch ","E 7.3 ","BOOL ","PPM306 - Product Recirc Pump Safety Switch " +"DI_PPM307_Contactor ","E 11.6 ","BOOL ","PPM307 - Booster Pump Feedback " +"DI_PPM307_Ovrld ","E 10.6 ","BOOL ","PPM307 - Booster Pump Ovrld " +"DI_PPM307_SafetySwitch ","E 7.6 ","BOOL ","PPM307 - Booster Pump Safety Switch " +"DI_PPM309_Contactor ","E 11.7 ","BOOL ","PPM309 - Product Pump 2 Feedback " +"DI_PPM309_Ovrld ","E 10.7 ","BOOL ","PPM309 - Product Pump 2 Overload " +"DI_PPM309_SafetySwitch ","E 7.7 ","BOOL ","PPM309 - Second Prod Pump Safety Switch " +"DI_PPN301_Contactor ","E 11.0 ","BOOL ","PPN301 - Deaireator Pump Feedback " +"DI_PPN301_Ovrld ","E 10.0 ","BOOL ","PPN301 - Deaireator Pump Overload " +"DI_PPN301_SafetySwitch ","E 7.0 ","BOOL ","PPN301 - Deaireator Pump Safety Switch " +"DI_PPN301_SoftStOvr ","E 12.0 ","BOOL ","PPN301 - Deaireator Pump Softstarter Ready " +"DI_PPN304_Contactor ","E 11.4 ","BOOL ","PPN304 - Vaccum Pump Feedback " +"DI_PPN304_Ovrld ","E 10.4 ","BOOL ","PPN304 - Vaccum Pump Overload " +"DI_PPN304_SafetySwitch ","E 7.4 ","BOOL ","PPN304 - Vacuum Pump Safety Switch " +"DI_PPN305_Contactor ","E 11.5 ","BOOL ","PPN305 - Deaireator Pump #2 Feedback " +"DI_PPN305_Ovrld ","E 10.5 ","BOOL ","PPN305 - Deaireator Pump #2 Overload " +"DI_PPN305_SafetySwitch ","E 7.5 ","BOOL ","PPN305 - Dear recirc Pump Safety Switch " +"DI_PPN305_SoftStOvr ","E 12.1 ","BOOL ","PPN305 - Deaireator Pump Softstarter Ready #2 Feedback " +"DI_PPP302_Contactor ","E 11.1 ","BOOL ","PPP302 - Syrup Pump Feedback " +"DI_PPP302_Ovrld ","E 10.1 ","BOOL ","PPP302 - Syrup Pump Overload " +"DI_PPP302_SafetySwitch ","E 7.1 ","BOOL ","PPP302 - Syrup Pump Safety Switch " +"DI_QTM405 ","E 32.1 ","BOOL "," allarme controllo PH " +"DI_RMM301_Closed ","E 119.0 ","BOOL ","RMM301 - Feedback OFF " +"DI_RMM303_Closed ","E 119.2 ","BOOL ","RMM303 - Feedback OFF " +"DI_RMM304_Closed ","E 119.3 ","BOOL ","RMM304 - Feedback OFF " +"DI_RMP302_Closed ","E 119.1 ","BOOL ","RMP302 - Feedback OFF " +"DI_SuperMaxPPN304 ","E 320.3 ","BOOL "," - PPN304 Water Level High High " +"DI_SuperMinPPN304 ","E 32.4 ","BOOL "," - PPN304 Water Level Low Low " +"DI_SurgeProtDevice_Ovrld","E 12.2 ","BOOL ","Feed back Overload Surge Protection Device " +"DI_SurgeProtectionDevice","E 6.0 ","BOOL ","Feed back Surge Protection Device " +"DI_SyrRoom_Cip_Mode ","E 68.3 ","BOOL ","Syrup Room - Cip Mode " +"DI_SyrRoom_CipRequest ","E 120.3 ","BOOL ","Cip Request " +"DI_SyrRoom_PrAvaiEndProd","E 120.2 ","BOOL ","Syrup Room - Product Avaiable End Production " +"DI_SyrRoom_Pump_Ready ","E 120.0 ","BOOL ","Syrup Room - Pump ready " +"DI_SyrRoom_SelectLine1 ","E 68.4 ","BOOL ","Syrup Room - Line 1 Selected " +"DI_SyrRoom_SelectLine2 ","E 68.5 ","BOOL ","Syrup Room - Line 2 Selected " +"DI_SyrRoom_SyrupRunOut ","E 120.1 ","BOOL ","Syrup Room - Run Out Request " +"DI_SyrRoom_WatPumpReady ","E 68.1 ","BOOL ","Syrup Room - Water Pump Ready " +"DI_UPSBatteryReady ","E 6.3 ","BOOL ","UPS Battery ready " +"DI_UPSFloatingOP ","E 6.1 ","BOOL ","UPS Floating Operation " +"DI_UPSOK ","E 6.2 ","BOOL ","UPS OK " +"DI_UV_Lamp_Ready ","E 9.0 ","BOOL ","Water UV Lamp Ready " +"DigitalInstrument ","UDT 84 ","UDT 84 "," " +"DIS_AIRT ","SFC 41 ","SFC 41 ","Delay the Higher Priority Interrupts and Asynchronous Errors " +"DO_AntonPaar_CarboStop ","A 7.1 ","BOOL ","DO_AntonPaar_CarboStop " +"DO_AntonPaar_Hold ","A 7.0 ","BOOL ","DO_AntonPaar_Hold " +"DO_AVM308 ","A 100.5 ","BOOL ","MIX - Product Tank Inlet " +"DO_AVM309 ","A 100.6 ","BOOL ","MIX - Product Tank Loading " +"DO_AVM311 ","A 100.3 ","BOOL ","MIX - Product Tank Blow Off " +"DO_AVM313 ","A 100.1 ","BOOL ","MIX - Product Recirculation " +"DO_AVM314 ","A 111.0 ","BOOL ","MIX - Product Recirculation 2 " +"DO_AVM315 ","A 100.4 ","BOOL ","MIX - Product Tank Drain " +"DO_AVM317 ","A 101.1 ","BOOL ","MIX - Product Tank Spray Ball " +"DO_AVM321 ","A 100.2 ","BOOL ","MIX - Product Tank Over Fill " +"DO_AVM322 ","A 100.7 ","BOOL ","MIX - Water Pump PPN301 Priming " +"DO_AVM324 ","A 105.0 ","BOOL ","MIX - Water Chiller Pipe Drain " +"DO_AVM326 ","A 102.0 ","BOOL ","MIX - CO2 Injection Shut Off " +"DO_AVM327 ","A 106.5 ","BOOL ","MIX - Blendfill Still Water By-Pass Mixproof 1 " +"DO_AVM328 ","A 106.6 ","BOOL ","MIX - Blendfill Still Water By-Pass Mixproof 2 " +"DO_AVM329 ","A 106.7 ","BOOL ","MIX - Blendfill Still Water By-Pass Mixproof 3 " +"DO_AVM331 ","A 106.0 ","BOOL ","MIX - No Blendfill Still Water By-Pass to Mixer " +"DO_AVM341 ","A 101.6 ","BOOL ","MIX - CO2 Inlet " +"DO_AVM342 ","A 105.7 ","BOOL ","MIX - N2 Inlet " +"DO_AVM343 ","A 101.7 ","BOOL ","MIX - Norgren High Pressure Protection " +"DO_AVM345 ","A 105.2 ","BOOL ","MIX - Product Chiller Inlet Drain " +"DO_AVM346 ","A 104.0 ","BOOL ","MIX - Filler CO2 " +"DO_AVM348 ","A 105.3 ","BOOL ","MIX - Product Chiller Outlet Drain " +"DO_AVM349 ","A 107.0 ","BOOL ","MIX - Flushing inlet valve " +"DO_AVM362 ","A 100.0 ","BOOL ","MIX - Product Outlet " +"DO_AVM363 ","A 111.1 ","BOOL ","MIX - Product Outlet 2 " +"DO_AVM369 ","A 330.5 ","BOOL ","MIX - Product pipe drain " +"DO_AVM370 ","A 109.4 ","BOOL ","MIX - Product pipe drain Outlet 2 " +"DO_AVM378 ","A 305.7 ","BOOL ","MIX - Product outlet switch " +"DO_AVM379 ","A 307.5 ","BOOL ","MIX - Water\Product Pipe Drain " +"DO_AVM381 ","A 105.4 ","BOOL ","MIX - Product Recirculation With Chiller " +"DO_AVM385 ","A 106.1 ","BOOL ","MIX - Still Water By-Pass Drain Inlet Water " +"DO_AVM386 ","A 106.2 ","BOOL ","MIX - Still Water By-Pass Drain Outlet Water " +"DO_AVM387 ","A 106.4 ","BOOL ","MIX - Blendfill Still Water By-Pass Product Interception " +"DO_AVM388 ","A 101.2 ","BOOL ","MIX - Drain Sanification Pipe " +"DO_AVM389 ","A 101.0 ","BOOL ","MIX - Sanification to Spray Balls " +"DO_AVM390 ","A 105.5 ","BOOL ","MIX - Chiller Glycol Return interception " +"DO_AVM391 ","A 105.6 ","BOOL ","MIX - Chiller Glycol Expansion " +"DO_AVM393 ","A 107.4 ","BOOL ","MIX - Product Pipe Drain - DBC Option " +"DO_AVM394 ","A 110.3 ","BOOL ","MIX - Booster Pipe Drain - DBC Option " +"DO_AVM396 ","A 320.0 ","BOOL ","MIX - Tank Pressurization with Steril Air " +"DO_AVM397 ","A 132.7 ","BOOL ","MIX - CO2 Buffer Tank Intercept " +"DO_AVM398 ","A 107.2 ","BOOL ","MIX - Blendfill Steril Air Inlet " +"DO_AVN301 ","A 102.1 ","BOOL ","MIX - Deaireator Water Inlet #1 " +"DO_AVN301_Status ","A 141.0 ","BOOL ","DO_AVN301_Status Open " +"DO_AVN302 ","A 102.2 ","BOOL ","MIX - Deaireator Water Inlet #2 " +"DO_AVN302_Status ","A 141.1 ","BOOL ","DO_AVN302_Status Open " +"DO_AVN303 ","A 102.3 ","BOOL ","MIX - Deaireator Water Inlet #3 " +"DO_AVN303_Status ","A 141.2 ","BOOL ","DO_AVN303_Status Open " +"DO_AVN305 ","A 103.1 ","BOOL ","MIX - Premixed Product Inlet " +"DO_AVN306 ","A 103.2 ","BOOL ","MIX - Premixed Product Priming " +"DO_AVN314 ","A 101.4 ","BOOL ","MIX - Deaireator Drain " +"DO_AVN318 ","A 102.4 ","BOOL ","MIX - Deaireator Over Fill " +"DO_AVN323 ","A 101.3 ","BOOL ","MIX - Water Pipe Drain " +"DO_AVN324 ","A 102.7 ","BOOL ","MIX - Deaireator 1st Stage Drain " +"DO_AVN327 ","A 101.5 ","BOOL ","MIX - Deaireator Tank Spray Ball " +"DO_AVN328 ","A 102.5 ","BOOL ","MIX - Deaireator Strip CO2 Injection " +"DO_AVN370 ","A 103.6 ","BOOL ","MIX - Inlet Refrigerant Vacuum Pump " +"DO_AVN371 ","A 103.7 ","BOOL ","MIX - Outlet Refrigerant Vacuum Pump " +"DO_AVN373 ","A 110.0 ","BOOL ","MIX - Deaireator Recirculation Pipe Drain " +"DO_AVN390 ","A 107.5 ","BOOL "," " +"DO_AVN395 ","A 102.6 ","BOOL ","MIX - Vacuum Pump Deaireator Intercept " +"DO_AVP315 ","A 104.4 ","BOOL ","MIX - Syrup Injection " +"DO_AVP316 ","A 104.3 ","BOOL ","MIX - Syrup Tank Drain " +"DO_AVP317 ","A 104.6 ","BOOL ","MIX - Syrup Cip Inlet " +"DO_AVP320 ","A 107.1 ","BOOL ","MIX - N2 Tank Syrup " +"DO_AVP324 ","A 104.2 ","BOOL ","MIX - Syrup Recirculation " +"DO_AVP344 ","A 104.5 ","BOOL ","MIX - Syrup Line Drain " +"DO_AVP361 ","A 104.1 ","BOOL ","MIX - Syrup Tank Spray Ball " +"DO_AVP363 ","A 110.7 ","BOOL ","MIX - Syrup Line In H2O " +"DO_AVP364 ","A 110.5 ","BOOL ","MIX - Syrup Line In Syrup 1 " +"DO_AVP365 ","A 110.6 ","BOOL ","MIX - Syrup Line In Syrup 2 " +"DO_AVP388 ","A 109.3 ","BOOL ","MIX - Syrup Cip Inlet with Local Cip " +"DO_AVS301 ","A 108.3 ","BOOL ","MIX - CIP Chemical Dosing " +"DO_AVS302 ","A 108.4 ","BOOL ","MIX - CIP Chemical Injection " +"DO_AVS341 ","A 108.1 ","BOOL ","MIX - CIP Loop Return " +"DO_AVS342 ","A 108.2 ","BOOL ","MIX - CIP Loop Return Drain " +"DO_AVS343 ","A 108.7 ","BOOL ","MIX - CIP Water Inlet Cleaning Return " +"DO_AVS344 ","A 109.2 ","BOOL ","MIX - CIP Syrup Inlet Cleaning Return " +"DO_AVS345 ","A 108.5 ","BOOL ","MIX - CIP Process Water Inlet Mixproof 1 " +"DO_AVS346 ","A 108.6 ","BOOL ","MIX - CIP Process Water Inlet Mixproof 2 " +"DO_AVS347 ","A 109.0 ","BOOL ","MIX - CIP Syrup Inlet Mixproof 1 " +"DO_AVS348 ","A 109.1 ","BOOL ","MIX - CIP Syrup Inlet Mixproof 2 " +"DO_AVS399 ","A 103.5 ","BOOL ","MIX - Drain Electrical Heat Exchanger " +"DO_BrixMeterON ","A 7.5 ","BOOL ","DO_BrixMeterON " +"DO_CIP_CleaningFault ","A 140.2 ","BOOL ","CIP - Cleaning Fault " +"DO_CIP_CleaningRequest ","A 140.1 ","BOOL ","CIP - Cleaning Request " +"DO_CIP_DrainCompleted ","A 140.3 ","BOOL ","CIP - Drain Completed " +"DO_CIP_FloodingCompleted","A 60.5 ","BOOL ","CIP - Flooding Completed " +"DO_CIP_RecoverCompleted ","A 60.3 ","BOOL ","CIP - Recover Completed " +"DO_CIP_SolutionReturn ","A 60.2 ","BOOL ","CIP - Solution Return " +"DO_CtrlCircuitRun ","A 5.1 ","BOOL ","Electrical Panel Reset " +"DO_EHS301 ","A 112.0 ","BOOL ","MIX - Heat Resistance #01 " +"DO_EHS302 ","A 112.1 ","BOOL ","MIX - Heat Resistance #02 " +"DO_EHS303 ","A 112.2 ","BOOL ","MIX - Heat Resistance #03 " +"DO_EHS304 ","A 112.3 ","BOOL ","MIX - Heat Resistance #04 " +"DO_EHS305 ","A 112.4 ","BOOL ","MIX - Heat Resistance #05 " +"DO_EHS306 ","A 112.5 ","BOOL ","MIX - Heat Resistance #06 " +"DO_Flr_CIP_CleaningEnd ","A 62.0 ","BOOL ","FILLER - Cip Cleaning Ended " +"DO_Flr_CIP_CleaningFault","A 62.1 ","BOOL ","FILLER - Cip Fault " +"DO_Flr_CIP_DrainRequest ","A 62.4 ","BOOL ","FILLER - Cip Drain Request " +"DO_Flr_CIP_ProdSending ","A 62.2 ","BOOL ","FILLER - Cip Product Sending " +"DO_Flr_CIP_RecoverReq ","A 62.3 ","BOOL ","FILLER - Cip Recover Request " +"DO_Flr_CIP_RinseMode ","A 63.0 ","BOOL ","FILLER - Rinse Mode " +"DO_Flr_CIP_RinseSending ","A 63.1 ","BOOL ","FILLER - Rinse Sending " +"DO_Flr_CIP_Running ","A 62.5 ","BOOL ","FILLER - Cip Running " +"DO_Flr_CIP_TankFilling ","A 62.6 ","BOOL ","FILLER - Flooding Request " +"DO_Flr_FastRinseRequest ","A 64.6 ","BOOL ","FILLER - Fast Change Over Rinse Request " +"DO_Flr_PROD_Available ","A 64.1 ","BOOL ","FILLER - Product Available " +"DO_Flr_PROD_Min_Level ","A 64.5 ","BOOL ","FILLER - Production Mode " +"DO_Flr_PROD_Mode ","A 64.0 ","BOOL ","FILLER - Production Mode " +"DO_Flr_PROD_Run_Out ","A 64.3 ","BOOL ","FILLER - Run Out Request " +"DO_Flr_ProductCompleted ","A 64.4 ","BOOL ","FILLER - Product Completed " +"DO_Green_Lamp ","A 6.0 ","BOOL ","DO_Green_Lamp " +"DO_HoldBrixMeter ","A 7.4 ","BOOL ","DO_HoldBrixMeter " +"DO_HoldO2Meter ","A 7.6 ","BOOL ","DO_HoldO2Meter " +"DO_HoldSyrupMeter ","A 7.7 ","BOOL ","DO_HoldSyrupMeter " +"DO_Horn ","A 6.3 ","BOOL ","DO_Horn " +"DO_Mixer_Status ","A 141.3 ","BOOL ","DO_Mixer_Status Production " +"DO_PPM303_Run ","A 11.2 ","BOOL ","DO_PPM303_Run " +"DO_PPM306_Run ","A 11.3 ","BOOL ","DO_PPM306_Run " +"DO_PPM307_Run ","A 12.0 ","BOOL ","DO_PPM307_Run " +"DO_PPM309_Run ","A 11.7 ","BOOL ","DO_PPM309_Run " +"DO_PPN301_Run ","A 11.0 ","BOOL ","DO_PPN301_Run " +"DO_PPN304_Run ","A 11.4 ","BOOL ","DO_PPN304_Run " +"DO_PPN305_Run ","A 11.5 ","BOOL ","DO_PPN305_Run " +"DO_PPP302_Run ","A 11.1 ","BOOL ","DO_PPP302_Run " +"DO_QTM305X ","A 109.6 ","BOOL ","Mix- Product control " +"DO_QTM305Y ","A 109.7 ","BOOL ","Mix- Cip control " +"DO_Red_Lamp ","A 6.2 ","BOOL ","DO_Red_Lamp " +"DO_RotorAlarm_Lamp ","A 6.1 ","BOOL ","DO_RotorAlarm_Lamp " +"DO_RVM301 ","A 103.4 ","BOOL ","MIX - Storage Tank Pressure Control Supply " +"DO_RVM301_Off ","A 11.6 ","BOOL ","DO_RVM301_Off " +"DO_RVM302 ","A 106.3 ","BOOL ","MIX - Blendfill Still Water Bypass Tank Level Control Supply " +"DO_RVM319 ","A 105.1 ","BOOL ","MIX - Chiller Water\Product Temperature Control Supply " +"DO_RVN302 ","A 103.3 ","BOOL ","MIX - Premixed Product Inlet Control Supply " +"DO_RVP303 ","A 104.7 ","BOOL ","MIX - Syrup Level Control Supply " +"DO_RVP305 ","A 110.4 ","BOOL ","MIX - Syrup 2 Level Control Supply " +"DO_RVS318 ","A 108.0 ","BOOL ","MIX - CIP Temperature Control Supply " +"DO_SyrupRoom_ValveOpen ","A 120.3 ","BOOL ","CIP-Syrup Room Valve Opened " +"DO_SyrupRoomPump_Run ","A 120.0 ","BOOL ","Syrup Room - Syrup Pump Request " +"DO_SyrupRoomWaterReq ","A 120.1 ","BOOL ","Syrup Room - Water Pump Request " +"DO_UPSSupplyCTRL ","A 5.3 ","BOOL ","UPS Supply Control " +"DO_Yellow_Lamp ","A 6.4 ","BOOL ","DO_Yellow_Lamp " +"DPNRM_DG ","SFC 13 ","SFC 13 ","Read Diagnostic Data of a DP Slave " +"DPRD_DAT ","SFC 14 ","SFC 14 ","Read Consistent Data of a Standard DP Slave " +"DPWR_DAT ","SFC 15 ","SFC 15 ","Write Consistent Data to a Standard DP Slave " +"EHS16 ","UDT 86 ","UDT 86 ","Electrical Heat Exchanger " +"EHS30X_16_Ctrl ","FC 1790 ","FC 1790 ","MIX - CIP Electric Heat Exchanger " +"EN_AIRT ","SFC 42 ","SFC 42 ","Enable Higher Priority Interrupts and Asynchronous Errors " +"ETH_Conveyor GEBO ","DB 41 ","FB 15 ","GEBO Conveyor ETH Comunication " +"ExtractPointerData ","FC 700 ","FC 700 "," " +"FastRinseReset ","M 1905.1 ","BOOL ","MIX - FP ****** " +"FeedForward ","FC 2002 ","FC 2002 ","MIX - Feed_Forward " +"FILL ","SFC 21 ","SFC 21 ","Initialize a Memory Area " +"Filler_Head_Variables ","DB 964 ","DB 964 ","MIX - Filler Head Variables " +"FillerControl ","FC 2028 ","FC 2028 ","MIX - Filler Control " +"FillerControl_DataSend ","FC 2029 ","FC 2029 ","MIX - Blend Fill Data Send " +"FillerGasBlowOff ","FC 1952 ","FC 1952 "," " +"FillerRinseReset ","M 1905.2 ","BOOL ","MIX - FP ****** " +"Filling_Time_Tranfer_DB ","DB 965 ","DB 965 "," " +"Filling_Time_Tranfer_Par","FC 1791 ","FC 1791 "," " +"FillingHeadIntegrInit ","M 1907.3 ","BOOL ","MIX - FP ****** " +"FillingTime ","FC 1840 ","FC 1840 ","MIX - FillingTime " +"FirstProduction ","FB 1805 ","FB 1805 ","MIX - First Production " +"FirstProduction_Data ","DB 935 ","FB 1805 ","MIX - First_Production_Data " +"FirstProductiontReset ","M 1907.1 ","BOOL ","MIX - FP ****** " +"Flow_To_Press_Loss ","FC 1843 ","FC 1843 ","MIX - Flow_To_Press_Loss " +"FN_CIPRepEndSupport ","M 601.1 ","BOOL ","FN - CIP Report End Support " +"FP_CIPRepStartSupport ","M 601.0 ","BOOL ","FP - CIP Report Start Support " +"FP_PRDRepEndSupport ","M 605.1 ","BOOL ","FP - PRODUCTION Report End Support " +"FP_PRDRepStartSupport ","M 605.0 ","BOOL ","FP - PRODUCTION Report Start Support " +"Freq_To_mmH2O ","FC 1841 ","FC 1841 ","MIX - Freq_To_mmH2O " +"FrictionLoss ","FC 2032 ","FC 2032 ","MIX - Friction_Loss " +"FTM303_Ctrl ","PAB 1060 ","BYTE ","MIX - " +"FTM303_Density ","PED 1070 ","REAL ","MIX - Profibus Variables " +"FTM303_Density_State ","PEB 1074 ","BYTE ","MIX - Profibus Variables " +"FTM303_EPD ","PEB 1060 ","BYTE ","MIX - Profibus Variables " +"FTM303_Flow ","PED 1060 ","REAL ","MIX - Profibus Variables " +"FTM303_Flow_State ","PEB 1064 ","BYTE ","MIX - Profibus Variables " +"FTM303_Temper_State ","PEB 1079 ","BYTE ","MIX - Profibus Variables " +"FTM303_Temperature ","PED 1075 ","REAL ","MIX - Profibus Variables " +"FTM303_Totalizer ","PED 1065 ","REAL ","MIX - Profibus Variables " +"FTM303_Totalizer_State ","PEB 1069 ","BYTE ","MIX - Profibus Variables " +"FTN301_Flow ","PED 1000 ","REAL ","MIX - Profibus Variables " +"FTN301_Flow_State ","PEB 1004 ","BYTE ","MIX - Profibus Variables " +"FTN301_Tot_Ctrl ","PAB 1000 ","BYTE ","MIX - " +"FTN301_Totaliz_State ","PEB 1009 ","BYTE ","MIX - Profibus Variables " +"FTN301_Totalizer ","PED 1005 ","REAL ","MIX - Profibus Variables " +"FTP302_Brix ","PED 1050 ","REAL ","MIX - Profibus Variables " +"FTP302_Brix_State ","PEB 1054 ","BYTE ","MIX - Profibus Variables " +"FTP302_Ctrl ","PAB 1030 ","BYTE ","MIX - " +"FTP302_Density ","PED 1040 ","REAL ","MIX - Profibus Variables " +"FTP302_Density_State ","PEB 1044 ","BYTE ","MIX - Profibus Variables " +"FTP302_Flow ","PED 1030 ","REAL ","MIX - Profibus Variables " +"FTP302_Flow_State ","PEB 1034 ","BYTE ","MIX - Profibus Variables " +"FTP302_S_EPD ","PEB 1030 ","BYTE ","MIX - Profibus Variables " +"FTP302_Temp ","PED 1045 ","REAL ","MIX - Profibus Variables " +"FTP302_Temp_State ","PEB 1049 ","BYTE ","MIX - Profibus Variables " +"FTP302_Totaliz_State ","PEB 1039 ","BYTE ","MIX - Profibus Variables " +"FTP302_Totalizer ","PED 1035 ","REAL ","MIX - Profibus Variables " +"FunctionButton ","UDT 21 ","UDT 21 "," " +"FW_DRand ","FC 1922 ","FC 1922 ","MIX - FW_DRand " +"g2StepPhasEN_AUX ","M 123.7 ","BOOL ","MIX - CIP 2 Step Phase Enable AUX " +"gActualSP_RatioVol ","MD 1964 ","REAL ","MIX - Target Volumetric Ratio " +"GADR_LGC ","SFC 5 ","SFC 5 ","Query Logical Address of a Channel " +"gAirLowPress_Fault ","M 1817.0 ","BOOL ","MIX - Supplies Alarm " +"gAlarmHorn ","M 1713.6 ","BOOL ","MIX - SAFETIES " +"gAVN301_EN ","M 1692.0 ","BOOL "," " +"gAVN302_EN ","M 1692.1 ","BOOL "," " +"gAVN303_EN ","M 1692.2 ","BOOL "," " +"gBalaiage_Fault ","M 1810.5 ","BOOL ","MIX - Balaiage Flow Error - Fault Alarm " +"gBalaiage_MaxFlow ","M 1733.7 ","BOOL ","MIX - Balaiage OK to Max Flow " +"gBalaiageTankFlowOK ","M 1740.6 ","BOOL ","MIX - Balaiage " +"gBFStillWatByPassN2Press","M 1716.7 ","BOOL ","MIX - Blend Fill Still Water ByPass N2 Pressurization " +"gBlenderAlarm ","M 1713.5 ","BOOL ","MIX - SAFETIES " +"gBlenderBlending ","M 1724.2 ","BOOL ","MIX - RUN CTRL " +"gBlenderCIPMode ","M 1741.7 ","BOOL ","MIX - " +"gBlenderEmpty ","M 1734.6 ","BOOL ","MIX - LEVEL " +"gBlenderEnToRamp ","M 1723.7 ","BOOL ","MIX - RUN CTRL " +"gBlenderFillerRunning ","M 1715.0 ","BOOL ","MIX - RUN CTRL - Both Blender and Filler are running " +"gBlenderFlowFltEn ","M 1724.6 ","BOOL ","MIX - RUN CTRL " +"gBlenderLevelOk ","M 1734.2 ","BOOL ","MIX - LEVEL - Product Tank Recipe Minimum Level Acheaved " +"gBlenderLevelTarget ","M 1734.3 ","BOOL ","MIX - LEVEL - Product Tank Target Level Acheaved " +"gBlenderMinLevel ","M 1734.4 ","BOOL ","MIX - LEVEL - Product Tank 5% Level Acheaved " +"gBlenderOperatorStop ","M 1724.4 ","BOOL ","MIX - RUN CTRL " +"gBlenderProdMode ","M 1741.5 ","BOOL ","MIX - " +"gBlenderRinseMode ","M 1741.6 ","BOOL ","MIX - " +"gBlendErrorHighSyr_Fault","M 1812.7 ","BOOL ","MIX - Fault Alarm " +"gBlendErrorLowSyr_Fault ","M 1813.0 ","BOOL ","MIX - Fault Alarm " +"gBlenderStableFlow ","M 1723.6 ","BOOL ","MIX - RUN CTRL " +"gBlenderStartLevel ","M 1734.1 ","BOOL ","MIX - LEVEL - Product Tank Pump Start Level Acheaved " +"gBlenderStartPumps ","M 1724.0 ","BOOL ","MIX - RUN CTRL " +"gBlenderStartPumpsONS ","M 1724.1 ","BOOL ","MIX - RUN CTRL " +"gBlenderStopLevel ","M 1734.0 ","BOOL ","MIX - LEVEL - Product Tank Maximum Level Acheaved " +"gBlenderStopping ","M 1724.3 ","BOOL ","MIX - RUN CTRL " +"gBlenderSuppliesOk ","M 1739.7 ","BOOL ","MIX - Air and CO2 pressure ok and auxiliary ok " +"gBlenderTankPress_Ok ","M 1738.2 ","BOOL ","MIX - PRESSURE - " +"gBlendFiStillWaterByPass","M 1716.1 ","BOOL ","MIX - " +"gBlendRecircActive ","M 1722.7 ","BOOL ","MIX - " +"gBlendResetTotalizer ","M 1741.2 ","BOOL ","MIX - " +"gBottleIn Filling ","M 300.6 ","BOOL ","From Filler : Bottle in Filling " +"gBypassGlicoleSysDelay ","T 93 ","TIMER ","MIX - " +"gCarboCO2Err_H_CO2_Fault","M 1813.1 ","BOOL ","MIX - Fault Alarm " +"gCarboCO2Err_L_CO2_Fault","M 1813.2 ","BOOL ","MIX - Fault Alarm " +"gCARBOStopProductMeter ","M 1740.3 ","BOOL ","MIX - ProdAnalizer - 0 : Stop _ 1 : On - CARBO Stop Signal To Product Meter " +"gCIP_AVM362_OP_En ","M 1735.4 ","BOOL ","MIX - CIP - System Level AVM362 Open Enable " +"gCIP_BlenderDrainDone ","M 1736.1 ","BOOL ","MIX - CIP - Drain " +"gCIP_DeairTank_Flood ","M 1736.2 ","BOOL ","MIX - CIP - Deaireator Tank Flood " +"gCIP_DeairTank_MaxLevel ","M 1737.4 ","BOOL ","MIX - LEVEL - Maximum Deaireator Tank Level during CIP " +"gCIP_PrdTank_Flood ","M 1736.0 ","BOOL ","MIX - CIP - Product Tank Flood " +"gCIP_PrdTank_MaxLevel ","M 1735.1 ","BOOL ","MIX - LEVEL - 100% Product Tank Level during CIP " +"gCIP_PrdTank_PressLow ","M 1736.4 ","BOOL ","MIX - CIP - Product Tank Low Pressure " +"gCIP_RecipePhaseLoadReq ","M 1716.0 ","BOOL ","MIX - " +"gCIP_SyrTank_Flood ","M 1736.3 ","BOOL ","MIX - CIP - Syrup Tank Flood " +"gCIP_SyrTank_MaxLevel ","M 1737.3 ","BOOL ","MIX - LEVEL - Maximum Syrup Tank Level during CIP " +"gCIP_SystemLevelOK ","M 1735.2 ","BOOL ","MIX - CIP - System Level OK " +"gCIP_SystemLowLevel ","M 1735.3 ","BOOL ","MIX - CIP - System Low Level " +"gCIPONS ","M 1721.0 ","BOOL ","MIX - " +"gCIPRecirFlowMiss_Fault ","M 1815.3 ","BOOL ","MIX - Fault Alarm " +"gCIPRinseEnableRMP302 ","M 1736.7 ","BOOL ","MIX - CIP/Rinse Enable RMP302 Command " +"gCIPRinseOpenRMP302 ","M 1736.6 ","BOOL ","MIX - CIP/Rinse Open RMP302 Command " +"gCIPRinseOpenValves ","M 1736.5 ","BOOL ","MIX - CIP/Rinse Open Valve Command " +"gCIPStopLevel ","M 1735.0 ","BOOL ","MIX - LEVEL - Maximum Tank Level during CIP " +"gClock_120s ","M 1950.0 ","BOOL ","MIX - Clock 120 Seconds (60 OFF - 60 ON) " +"gClock_20s ","M 1950.3 ","BOOL ","MIX - Clock 20 Seconds (10 OFF - 10 ON) " +"gClock_240s ","M 1950.4 ","BOOL ","MIX - Clock 240 Seconds (120 OFF - 120 ON) " +"gClock_40s ","M 1950.2 ","BOOL ","MIX - Clock 40 Seconds (20 OFF - 20 ON) " +"gClock_60s ","M 1950.1 ","BOOL ","MIX - Clock 60 Seconds (30 OFF - 30 ON) " +"gCloseMainCO2Valve ","M 1739.4 ","BOOL ","MIX - PRESSURE - " +"gCO2LowPress_Fault ","M 1817.1 ","BOOL ","MIX - Supplies Alarm " +"gCO2PressNotOk ","M 1742.5 ","BOOL ","MIX - " +"gColingSysBypassResetFP ","M 1718.4 ","BOOL ","MIX - " +"gColingSystemBypassReset","M 1718.2 ","BOOL ","MIX - Cooling System Bypass Reset " +"gCoolingSysBypassAct ","M 1718.0 ","BOOL ","Mix - System Bypass Actived " +"gCoolingSystemBypass ","M 1718.1 ","BOOL ","MIX - Cooling System Bypass Reset " +"gCoolingSystemBypassFP ","M 1718.3 ","BOOL ","Mix - " +"gCoolingTempMaxLowAlrm ","M 1719.0 ","BOOL ","Mix - Glycole temperature Max Low Alarm " +"gCriticalBlending ","M 1723.0 ","BOOL ","MIX - " +"gDeairLSN301Lvl_Fault ","M 1818.5 ","BOOL ","MIX - Supplies Alarm " +"gDeairTank_HighLvl_Fault","M 1814.1 ","BOOL ","MIX - Deair Tank High Level Fault - Fault Alarm " +"gDeairTank_Loading ","M 1723.4 ","BOOL ","MIX - Deaireator Tank Loading " +"gDeairTank_LowLvl_Fault ","M 1817.2 ","BOOL ","MIX - Deaireator Tank Low Level Fault - Supplies Alarm " +"gDeairTank_VacuumOk ","M 1739.3 ","BOOL ","MIX - PRESSURE - Deaireator vacuum pressure acheaved " +"gDeviceFault_ICS ","M 1713.2 ","BOOL ","MIX - SAFETIES " +"gDeviceFault_MIX ","M 1713.1 ","BOOL ","MIX - SAFETIES " +"gDiffSensor_Analog_Fault","M 1816.2 ","BOOL ","MIX - Fault Alarm " +"gDrainPressNotOK ","M 1742.6 ","BOOL ","MIX - " +"gEHS_ActivatedRes ","MW 500 ","WORD "," " +"gEHS301_Activated ","M 500.0 ","BOOL ","MIX - ICS EHS " +"gEHS301_Run ","M 504.0 ","BOOL ","MIX - ICS EHS " +"gEHS302_Activated ","M 500.1 ","BOOL ","MIX - ICS EHS " +"gEHS302_Run ","M 504.1 ","BOOL ","MIX - ICS EHS " +"gEHS303_Activated ","M 500.2 ","BOOL ","MIX - ICS EHS " +"gEHS303_Run ","M 504.2 ","BOOL ","MIX - ICS EHS " +"gEHS304_Activated ","M 500.3 ","BOOL ","MIX - ICS EHS " +"gEHS304_Run ","M 504.3 ","BOOL ","MIX - ICS EHS " +"gEHS305_Activated ","M 500.4 ","BOOL ","MIX - ICS EHS " +"gEHS305_Run ","M 504.4 ","BOOL ","MIX - ICS EHS " +"gEHS306_Activated ","M 500.5 ","BOOL ","MIX - ICS EHS " +"gEHS306_Run ","M 504.5 ","BOOL ","MIX - ICS EHS " +"gEHS307_Activated ","M 500.6 ","BOOL ","MIX - ICS EHS " +"gEHS307_Run ","M 504.6 ","BOOL ","MIX - ICS EHS " +"gEHS308_Activated ","M 500.7 ","BOOL ","MIX - ICS EHS " +"gEHS308_Run ","M 504.7 ","BOOL ","MIX - ICS EHS " +"gEHS309_Activated ","M 501.0 ","BOOL ","MIX - ICS EHS " +"gEHS309_Run ","M 505.0 ","BOOL ","MIX - ICS EHS " +"gEHS30X_TTS399High ","M 502.0 ","BOOL ","MIX - ICS EHS " +"gEHS310_Activated ","M 501.1 ","BOOL ","MIX - ICS EHS " +"gEHS310_Run ","M 505.1 ","BOOL ","MIX - ICS EHS " +"gEHS311_Activated ","M 501.2 ","BOOL ","MIX - ICS EHS " +"gEHS311_Run ","M 505.2 ","BOOL ","MIX - ICS EHS " +"gEHS312_Activated ","M 501.3 ","BOOL ","MIX - ICS EHS " +"gEHS312_Run ","M 505.3 ","BOOL ","MIX - ICS EHS " +"gEHS313_Activated ","M 501.4 ","BOOL ","MIX - ICS EHS " +"gEHS313_Run ","M 505.4 ","BOOL ","MIX - ICS EHS " +"gEHS314_Activated ","M 501.5 ","BOOL ","MIX - ICS EHS " +"gEHS314_Run ","M 505.5 ","BOOL ","MIX - ICS EHS " +"gEHS315_Activated ","M 501.6 ","BOOL ","MIX - ICS EHS " +"gEHS315_Run ","M 505.6 ","BOOL ","MIX - ICS EHS " +"gEHS316_Activated ","M 501.7 ","BOOL ","MIX - ICS EHS " +"gEHS316_Run ","M 505.7 ","BOOL ","MIX - ICS EHS " +"gElCabTooHigh ","M 1818.1 ","BOOL ","MIX - Supplies Alarm " +"gEmergencyPressed ","M 1713.0 ","BOOL ","MIX - " +"gEnRampDownToStop ","M 1714.7 ","BOOL ","MIX - RUN CTRL " +"gEnRecircPHE ","M 1715.5 ","BOOL ","MIX - " +"gEnToStopBlender ","M 1722.5 ","BOOL ","MIX - RUN CTRL - Next Filler stop, stop blender " +"gEqPressOk ","M 1739.1 ","BOOL ","MIX - PRESSURE - Equilibrium pressure acheaved during First Production " +"gEqPressSelected ","M 1738.7 ","BOOL ","MIX - PRESSURE - " +"GetProdBrixCO2_Anal_Inpt","FC 1894 ","FC 1894 "," " +"gFastChOver_TKFastDrain ","M 304.6 ","BOOL ","Fast Change Over: Product Tank Fast Drain " +"gFastChOver_TKNotEmpty ","M 304.5 ","BOOL ","Fast Change Over: Product Tank Empty " +"gFillerEqPressRec ","M 1742.4 ","BOOL ","Filler Equil Pressure Recovery " +"gFillerTankDrain_FP ","M 301.3 ","BOOL ","M3013 " +"gFilling_Time_Sel ","M 300.0 ","BOOL "," " +"gFinalPressureOk ","M 1739.0 ","BOOL ","MIX - PRESSURE - Final Pressure acheaved during First Production " +"gFirstProdPressHold ","M 1739.2 ","BOOL ","MIX - PRESSURE - Pressure control enable during first production " +"gFirstProdStop ","M 1734.7 ","BOOL ","MIX - LEVEL - Stop Blender when First Production Done " +"gFlowToFiller ","M 1722.6 ","BOOL ","MIX - RUN CTRL - Product is Flowing to Filler " +"gFreezeProductMeter ","M 1740.1 ","BOOL ","MIX - ProdAnalizer - Hold Signal To Product Meter " +"gFreezeSyrupMeter ","M 1740.0 ","BOOL ","MIX - ProdAnalizer - Hold Signal To Syrup Densimeter " +"gFTM303_EPD ","M 1811.1 ","BOOL ","MIX - CO2 MFM EPD - Fault Alarm " +"gFTM303_Fault ","M 1811.5 ","BOOL ","MIX - CO2 MFM Fault - Fault Alarm " +"gFTM303_ResetTot ","M 1742.2 ","BOOL ","MIX - CO2 MFM Reset Tot " +"gFTN301_EPD ","M 1810.7 ","BOOL ","MIX - Water VFM EPD - Fault Alarm " +"gFTN301_Fault ","M 1811.3 ","BOOL ","MIX - Water VFM Fault - Fault Alarm " +"gFTN301_FlowTooLow ","M 1812.5 ","BOOL ","MIX - Water Flow Too Low - Fault Alarm " +"gFTN301_ResetTot ","M 1742.0 ","BOOL ","MIX - Water VFM Reset Totalizer " +"gFTN301_SpeedTooLow ","M 1812.6 ","BOOL ","MIX - Water Speed Too Low - Fault Alarm " +"gFTN301_SpeedToStop ","M 1714.5 ","BOOL ","MIX - Water Speed To Stop - RUN CTRL " +"gFTP302_EPD ","M 1811.0 ","BOOL ","MIX - Syrup MFM EPD - Fault Alarm " +"gFTP302_Fault ","M 1811.4 ","BOOL ","MIX - Syrup MFM - Fault Alarm " +"gFTP302_ResetTot ","M 1742.1 ","BOOL ","MIX - Syrup MFM Reset Totalizer " +"gGencoldChillerEn ","M 1714.0 ","BOOL ","MIX - " +"gHighPriorityAlarm ","M 1713.3 ","BOOL ","MIX - SAFETIES " +"gHVP301_Open ","M 1820.0 ","BOOL ","MIX - Manual Syrup Drain Valve Open - Operator Alarm " +"gICSCheckCond ","M 1712.0 ","BOOL ","MIX - ICS Check Conductivity " +"gICSDosingAfterVolume ","M 1712.1 ","BOOL ","MIX - Enabling Dosing after Volume " +"gICSManualSendingEN ","M 264.3 ","BOOL ","MIX - ICS Manual Sending Enable " +"gIN_CIP_CausticSanRun ","M 1500.3 ","BOOL ","MIX - From CIP Room CIP Change Sanitize " +"gIN_CIP_ChangeSanitize ","M 1500.4 ","BOOL ","MIX - From CIP Room CIP change sanitize " +"gIN_CIP_ChemicalProd ","M 1500.5 ","BOOL ","Mix - From CIPRoom " +"gIN_CIP_CIPCompleted ","M 1500.6 ","BOOL ","MIX - From CIP Room CIP completed " +"gIN_CIP_CIPMode ","M 1500.7 ","BOOL ","MIX - From CIP Room CIP Mode " +"gIN_CIP_CIPProdSending ","M 1501.0 ","BOOL "," " +"gIN_CIP_CIPRunning ","M 1501.1 ","BOOL ","MIX - From CIP Room CIP running " +"gIN_CIP_DrainRequest ","M 1501.2 ","BOOL ","MIX - From CIP Room Drain Request " +"gIN_CIP_Fault ","M 1501.3 ","BOOL ","MIX - From CIP Room CIP Fault " +"gIN_CIP_FreeSodaTank ","M 1501.4 ","BOOL ","MIX - CIP Soda Tank is able to receive soda from mixer " +"gIN_CIP_HotPhaseEnded ","M 1499.0 ","BOOL ","MIX - From CIP Room Hot Phase Ended " +"gIN_CIP_HotWaterSending ","M 1501.5 ","BOOL ","MIX - CIP Hot Water Sending to Mixer " +"gIN_CIP_HotWaterStop ","M 1500.0 ","BOOL ","MIX - CIP Hot Water Stop Sending " +"gIN_CIP_RinseMode ","M 1501.6 ","BOOL ","MIX - From CIP Room Rinse Mode " +"gIN_CIP_TankFilling ","M 1501.7 ","BOOL ","MIX - From CIP Room Flood Request " +"gIN_CO2FilterJam ","M 1504.2 ","BOOL ","MIX - CO2 Steril Filter Jam " +"gIN_DeairPPN305LevelOK ","M 1502.1 ","BOOL ","MIX - PPN305 Level Ok to Run " +"gIN_DeairTank_MinLvl ","M 1502.0 ","BOOL ","MIX - Deaireator Tank Minimum Level " +"gIN_DSAirSupplyTooLow ","M 1510.0 ","BOOL ","MIX - ICS Air Pressure Low " +"gIN_DSChemicalSelectioOK","M 1510.4 ","BOOL ","MIX - ICS Chemical Selection OK " +"gIN_DSConcLowLevel ","M 1510.2 ","BOOL ","MIX - ICS Concentrate Low Level " +"gIN_DSConcNotAvailable ","M 1510.1 ","BOOL ","MIX - ICS Concentrate Not Available " +"gIN_DSFault ","M 1510.3 ","BOOL ","MIX - ICS Generic Fault " +"gIn_DSFTK200Fault ","M 1510.6 ","BOOL ","MIX - ICS FTK200 Fault " +"gIN_DSVolumeDosingOK ","M 1510.5 ","BOOL ","MIX - ICS Volume to be Dosed Achieved " +"gIN_Filler_AVM346_En ","M 1502.2 ","BOOL ","MIX - From Filler AVM346 Enable " +"gIN_Filler_AVM362_En ","M 1502.3 ","BOOL ","MIX - From Filler AVM362 Enable " +"gIN_FillerBottleFilling ","M 1502.4 ","BOOL ","MIX - " +"gIN_FillerCIPChangeReady","M 1502.5 ","BOOL ","MIX - From Filler Spare " +"gIN_FillerCIPCycleEnded ","M 1502.6 ","BOOL ","MIX - From Filler Spare " +"gIN_FillerCIPDrainCompl ","M 1502.7 ","BOOL ","MIX - From Filler Drain Completed " +"gIN_FillerCIPDrainSelect","M 1503.0 ","BOOL ","MIX - From Filler Drain Selecteted " +"gIN_FillerCIPModeSelect ","M 1503.1 ","BOOL ","MIX - From Filler CIP Mode Selected " +"gIN_FillerCIPOpen_AVM313","M 1503.2 ","BOOL ","MIX - From Filler CIP Open AVM313 " +"gIN_FillerCIPOpen_AVM314","M 1508.6 ","BOOL ","MIX - From Filler CIP Open AVM314 " +"gIN_FillerCIPRequest ","M 1503.3 ","BOOL ","MIX - From Filler Ready to CIP " +"gIN_FillerCIPStopRetPump","M 1503.4 ","BOOL ","MIX - " +"gIN_FillerEmptyOfBottles","M 1500.2 ","BOOL ","MIX - Combi Empty of Bottles " +"gIN_FillerEndFlushing ","M 1503.5 ","BOOL ","MIX - " +"gIN_FillerEndTankFilling","M 1503.6 ","BOOL ","MIX - " +"gIN_FillerPRODDrainREQ ","M 1603.1 ","BOOL ","From Filler Drain Req during production " +"gIN_FillerProdReady ","M 1503.7 ","BOOL ","MIX - From Filler Ready to Production " +"gIN_FillerProdRunOutReq ","M 1504.0 ","BOOL ","MIX - From Filler Product Run Out request " +"gIN_FillerProductReq ","M 1504.1 ","BOOL ","MIX - From Filler Product request " +"gIN_HVP301_Aux ","M 1504.3 ","BOOL ","MIX - Syrup Line Drain Valve Aux " +"gIN_Line1_CIP_Ready ","M 1504.4 ","BOOL ","MIX - From Line1 Ready to CIP " +"gIN_Line1_end_Drain ","M 1504.5 ","BOOL ","MIX - From Line1 Drain Completed " +"gIN_Line1_spare ","M 1504.6 ","BOOL ","MIX - From Line1 Spare " +"gIN_LinePressCO2Ok ","M 1504.7 ","BOOL ","MIX - " +"gIN_ManifoldNotReadyCIP ","M 1505.0 ","BOOL ","MIX - " +"gIN_ManifoldNotReadyProd","M 1505.1 ","BOOL ","MIX - " +"gIN_MaxPPN304 ","M 1505.2 ","BOOL ","MIX - Maximum level vacuum pump " +"gIN_MinPPN304 ","M 1505.3 ","BOOL ","MIX - Minimum level vacuum pump " +"gIN_PPM303_FCReady ","M 1505.4 ","BOOL ","MIX - Product Pump FC Ready " +"gIN_PPM306_FCReady ","M 1508.5 ","BOOL ","MIX - Recirculation Pump FC Ready " +"gIN_PPM307_FCReady ","M 1509.5 ","BOOL ","MIX - Booster Pump FC Ready " +"gIN_PPN301_FCReady ","M 1505.5 ","BOOL ","MIX - Water Pump FC Ready " +"gIN_PPN304_FCReady ","M 1509.4 ","BOOL ","MIX - Vacuum Pump FC Ready " +"gIN_PPP302_FCReady ","M 1505.6 ","BOOL ","MIX - Syrup Pump FC Ready " +"gIN_Reset_VLT_CTRL ","M 1506.0 ","BOOL "," " +"gIN_ResetBtn ","M 1505.7 ","BOOL ","MIX - " +"gIN_ResetHorn ","M 1508.0 ","BOOL ","MIX - " +"gIN_RMM301_Closed ","M 1508.1 ","BOOL ","MIX - Water Valve Closed " +"gIN_RMM303_Closed ","M 1508.2 ","BOOL ","MIX - Carbo CO2 Valve Closed " +"gIN_RMM304_Closed ","M 1508.3 ","BOOL ","MIX - GAS2 Valve Closed " +"gIN_RMP302_Closed ","M 1508.4 ","BOOL ","MIX - Syrup Valve Closed " +"gIN_StartBtn ","M 1508.7 ","BOOL ","MIX - " +"gIN_StopBtn ","M 1509.0 ","BOOL ","MIX - " +"gIN_SyrRoomLast400l ","M 1507.0 ","BOOL ","MX - From Syrup Room DI Last 400 liters of Syrup " +"gIN_SyrRoomLast400lRunno","M 1507.1 ","BOOL ","MX - From Syrup Room Last 400 liters of Syrup running " +"gIN_SyrRoomOk ","M 1509.1 ","BOOL ","MIX - From Syrup Room Syrup ready " +"gIN_SyrTank_MinLvl ","M 1509.2 ","BOOL ","MIX - Syrup Tank Minimum Level " +"gIN_VoltageOk ","M 1509.3 ","BOOL ","MIX - " +"gInstrumentFault ","M 1716.3 ","BOOL ","MIX - SAFETIES " +"GLOBAL_DIAG_DB ","DB 11 ","DB 11 ","Profibus Network Diagnostic Data " +"GLOBAL_DP_DIAG ","FC 125 ","FC 125 "," " +"gLowPriorityAlarm ","M 1713.4 ","BOOL ","MIX - SAFETIES " +"gMaselli_AlcoholVolume ","PED 1316 ","REAL ","MIX - Profibus Variables " +"gMaselli_Error_Fault ","M 1814.6 ","BOOL ","MIX - Fault Alarm " +"gMaselli_ProdPerStandard","PED 1308 ","REAL ","MIX - Profibus Variables " +"gMaselli_ProductBrix ","PED 1300 ","REAL ","MIX - Profibus Variables " +"gMaselli_ProductCO2 ","PED 1304 ","REAL ","MIX - Profibus Variables " +"gMaselli_ProductNumber ","PEB 1328 ","BYTE ","MIX - Profibus Variables " +"gMaselli_ProductTemp ","PED 1312 ","REAL ","MIX - Profibus Variables " +"gMaselli_ProfibusStatus ","PEB 1330 ","BYTE ","MIX - Profibus Variables " +"gMaselli_RecipeSetNum ","PAB 1332 ","BYTE ","MIX - Profibus Variables " +"gMaselli_RecipeSetNumStr","PAB 1333 ","BYTE ","MIX - Profibus Variables " +"gMaselli_SetChangeOver ","PAB 1337 ","BYTE ","MIX - Profibus Variables " +"gMaselli_SetFlagProduct ","PAB 1338 ","BYTE ","MIX - Profibus Variables " +"gMaselliUC05_Com_Fault ","M 1815.0 ","BOOL ","MIX - Fault Alarm " +"gMaselliUC05_Error_Fault","M 1814.7 ","BOOL ","MIX - Fault Alarm " +"gMaselliUR22_Com_Fault ","M 1815.2 ","BOOL ","MIX - Fault Alarm " +"gMaselliUR22_Error_Fault","M 1815.1 ","BOOL ","MIX - Fault Alarm " +"gMaxBlendErrorAfterFault","M 1741.3 ","BOOL ","MIX - " +"gMaxCarboErrorAfterFault","M 1741.4 ","BOOL ","MIX - " +"gMaxSyrAutoCorrDone ","M 1740.5 ","BOOL ","MIX - Maximum Syrup Brix Autocorr Acheaved maximum autocorr perc " +"gMinRatio ","M 1740.4 ","BOOL ","MIX - Maximum Syrup Brix Autocorr Acheaved - minimum ratio - " +"gModValveRiseTimeCalcEn ","M 1724.5 ","BOOL ","MIX - " +"gmPDS_PA_Error_Fault ","M 1816.6 ","BOOL ","MIX - Fault Alarm " +"gmPDS_SYR_PA_Error_Fault","M 1814.0 ","BOOL ","MIX - Fault Alarm " +"gmPDS1000_Error_Fault ","M 1814.3 ","BOOL ","MIX - Fault Alarm " +"gmPDS1000_NoOscillation ","M 1814.4 ","BOOL ","MIX - Fault Alarm " +"gmPDS2000_Error_Fault ","M 1814.5 ","BOOL ","MIX - Fault Alarm " +"gNotARecipe_Fault ","M 1812.4 ","BOOL ","MIX - Fault Alarm " +"GNS DriveDiag ","FB 1601 ","FB 1601 ","Single Drive Diagnostic Read " +"GNS DriveDiag DB ","DB 1021 ","FB 1600 ","Drive Diagnostic Management Data " +"GNS DriveDiagMain ","FB 1600 ","FB 1600 ","Drive Diagnostic Management " +"gOUT_CIPChangeReady ","M 1600.0 ","BOOL ","MIX - To CIP Room " +"gOUT_CIPCycleEnded ","M 1600.1 ","BOOL ","MIX - To CIP Room " +"gOUT_CIPDrainCompleted ","M 1600.2 ","BOOL ","MIX - To CIP Room " +"gOUT_CIPHotWaterRequest ","M 1600.3 ","BOOL ","MIX - To CIP Room " +"gOUT_CIPSendSodaRequest ","M 1600.4 ","BOOL ","MIX - To CIP Room " +"gOUT_CIPWaterPipe_Ready ","M 1600.5 ","BOOL ","MIX - To CIP Room " +"gOUT_FillerCIPCaSanitRun","M 1600.6 ","BOOL ","MIX - To Filler CIP (FillerCIPCausticSanitizeRunning) " +"gOUT_FillerCIPChangeSani","M 1600.7 ","BOOL ","MIX - To Filler CIP Change sanitize " +"gOUT_FillerCIPCompleted ","M 1601.0 ","BOOL ","MIX - To Filler CIP Completed " +"gOUT_FillerCIPDrainReq ","M 1601.1 ","BOOL ","MIX - To Filler CIP Drain " +"gOUT_FillerCIPHotEnd ","M 1603.2 ","BOOL ","MIX - To Filler - To Filler CIP Hot Phase Ended " +"gOUT_FillerCIPMode ","M 1601.2 ","BOOL ","MIX - To Filler CIP Request " +"gOUT_FillerCIPRunning ","M 1601.3 ","BOOL ","MIX - To Filler CIP Running " +"gOUT_FillerMinimumLevel ","M 1601.4 ","BOOL ","MIX - To Filler Product under minimum level " +"gOUT_FillerProductAvail ","M 1601.5 ","BOOL ","MIX - To Filler Product sending available " +"gOUT_FillerProduction ","M 1601.6 ","BOOL ","MIX - To Filler Production " +"gOUT_FillerRinseMode ","M 1601.7 ","BOOL ","MIX - To Filler Rinse Request " +"gOUT_PPM303_Run ","M 1602.0 ","BOOL ","MIX - PPM303 Product Pump Run " +"gOUT_PPM306_Run ","M 1602.1 ","BOOL ","MIX - PPM306 Recycle Pump Run " +"gOUT_PPM307_Run ","M 1602.7 ","BOOL ","MIX - PPM307 Booster Pump Run " +"gOUT_PPM309_Run ","M 1603.0 ","BOOL ","MIX - PPM309 Product Pump 2 Run " +"gOUT_PPN301_Run ","M 1602.2 ","BOOL ","MIX - PPN301 Water Pump Run " +"gOUT_PPN301_VFCRun ","M 1602.3 ","BOOL ","MIX - PPN301 Water Pump VFC Run " +"gOUT_PPN304_Run ","M 1602.4 ","BOOL ","MIX - PPN304 Vacuum Pump Run " +"gOUT_PPN305_Run ","M 1602.5 ","BOOL ","MIX - PPN305 Water Pump Run " +"gOUT_PPP302_Run ","M 1602.6 ","BOOL ","MIX - PPP302 Syrup Pump Run " +"gP_AVM308 ","M 1400.4 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM309 ","M 1400.5 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM311 ","M 1400.6 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM313 ","M 1400.7 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM314 ","M 1402.6 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM315 ","M 1401.0 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM317 ","M 1401.1 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM321 ","M 1401.2 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM322 ","M 1401.3 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM324 ","M 1401.4 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM326 ","M 1401.6 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM327 ","M 1401.7 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM328 ","M 1402.0 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM329 ","M 1402.1 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM331 ","M 1402.3 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM341 ","M 1402.4 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM342 ","M 1402.5 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM345 ","M 1403.0 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM346 ","M 1403.1 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM348 ","M 1403.3 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM349 ","M 1403.4 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM362 ","M 1403.6 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM363 ","M 1402.7 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM369 ","M 1405.3 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM370 ","M 1405.4 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM378 ","M 1405.5 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM379 ","M 1405.6 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM381 ","M 1403.7 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM385 ","M 1404.0 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM386 ","M 1404.1 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM387 ","M 1404.2 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM388 ","M 1404.3 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM389 ","M 1404.4 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM390 ","M 1404.5 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM391 ","M 1404.6 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM393 ","M 1413.4 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM394 ","M 1413.5 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM396 ","M 1405.0 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM397 ","M 1405.1 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVM398 ","M 1405.2 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVN301 ","M 1406.0 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVN302 ","M 1406.1 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVN303 ","M 1406.2 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVN305 ","M 1406.4 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVN306 ","M 1406.5 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVN30x_En ","M 1420.0 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVN314 ","M 1407.0 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVN318 ","M 1407.2 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVN323 ","M 1407.3 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVN324 ","M 1407.4 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVN327 ","M 1407.7 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVN328 ","M 1408.0 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVN329 ","M 1408.1 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVN370 ","M 1408.2 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVN371 ","M 1408.3 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVN373 ","M 1408.5 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVN390 ","M 1413.7 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVN395 ","M 1408.6 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVP315 ","M 1409.2 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVP316 ","M 1409.3 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVP317 ","M 1409.4 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVP320 ","M 1422.7 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVP324 ","M 1409.7 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVP344 ","M 1410.0 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVP361 ","M 1410.2 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVP363 ","M 1410.4 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVP364 ","M 1410.5 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVP365 ","M 1410.6 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVP388 ","M 1411.7 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVS301 ","M 1412.0 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVS302 ","M 1412.1 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVS341 ","M 1412.4 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVS342 ","M 1412.5 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVS343 ","M 1412.6 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVS344 ","M 1412.7 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVS345 ","M 1413.0 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVS346 ","M 1413.1 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVS347 ","M 1413.2 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVS348 ","M 1413.3 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_AVS399 ","M 1413.6 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_BldTankPress_En ","M 1415.0 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_CarboPipe_En ","M 1415.1 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_CIP_CO2_Inj ","M 1415.2 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_CIP_Temp_En ","M 1415.3 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_CIPChangeSanitize ","M 1415.4 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_CIPDrainRequest ","M 1415.5 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_CIPHotPhase ","M 1415.6 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_CIPRunning ","M 1415.7 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_DSConcBooking ","M 1422.1 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_DSDosingCompleted ","M 1422.3 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_DSFlushBooking ","M 1422.2 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_DSFlushCompleted ","M 1422.4 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_DSSendEnable ","M 1422.5 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_DSVolumeBooking ","M 1422.0 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_FillerFlushingRun ","M 1420.7 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_FillerProdLoad ","M 1416.1 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_FillerProdSend ","M 1416.2 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_HotWaterRequest ","M 1416.3 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_LimitCO2PressValve ","M 1416.4 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_LoadingLevel ","M 1422.6 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_PID_Head_Enable ","M 1416.5 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_PID_Head_Manual ","M 1416.6 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_PPM303 ","M 1418.4 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_PPM307 ","M 1418.5 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_PPM309 ","M 1418.6 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_PPN301 ","M 1418.0 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_PPN304 ","M 1418.1 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_PPN305 ","M 1418.2 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_PPP302 ","M 1418.3 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_QTM305X ","M 1423.0 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_QTM305Y ","M 1423.1 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_RVM302_En ","M 1420.5 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_RVM302_Fixed ","M 1420.6 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_RVN302_En ","M 1420.1 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_RVN302_Fixed ","M 1420.2 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_RVP303_En ","M 1420.3 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_SendSodaRequest ","M 1416.7 ","BOOL ","MIX - Procedure auxiliary variable " +"gP_SyrRoomPump ","M 1417.0 ","BOOL ","MIX - Procedure auxiliary variable " +"gPAMaselli_Error_Fault ","M 1816.7 ","BOOL ","MIX - Fault Alarm " +"gPAmPDS_INBlock01_0 ","PEB 1150 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock01_1 ","PEB 1151 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock01_10 ","PEB 1160 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock01_11 ","PEB 1161 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock01_12 ","PEB 1162 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock01_13 ","PEB 1163 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock01_14 ","PEB 1164 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock01_15 ","PEB 1165 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock01_16 ","PEB 1166 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock01_17 ","PEB 1167 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock01_18 ","PEB 1168 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock01_19 ","PEB 1169 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock01_2 ","PEB 1152 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock01_20 ","PEB 1170 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock01_21 ","PEB 1171 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock01_22 ","PEB 1172 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock01_23 ","PEB 1173 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock01_24 ","PEB 1174 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock01_25 ","PEB 1175 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock01_26 ","PEB 1176 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock01_27 ","PEB 1177 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock01_28 ","PEB 1178 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock01_29 ","PEB 1179 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock01_3 ","PEB 1153 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock01_4 ","PEB 1154 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock01_5 ","PEB 1155 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock01_6 ","PEB 1156 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock01_7 ","PEB 1157 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock01_8 ","PEB 1158 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock01_9 ","PEB 1159 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock02_30 ","PEB 1180 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock02_31 ","PEB 1181 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock02_32 ","PEB 1182 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock02_33 ","PEB 1183 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock02_34 ","PEB 1184 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock02_35 ","PEB 1185 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock02_36 ","PEB 1186 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock02_37 ","PEB 1187 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock02_38 ","PEB 1188 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock02_39 ","PEB 1189 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock02_40 ","PEB 1190 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock02_41 ","PEB 1191 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock02_42 ","PEB 1192 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock02_43 ","PEB 1193 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock02_44 ","PEB 1194 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock02_45 ","PEB 1195 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock02_46 ","PEB 1196 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock02_47 ","PEB 1197 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock02_48 ","PEB 1198 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock02_49 ","PEB 1199 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock02_50 ","PEB 1200 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock02_51 ","PEB 1201 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock02_52 ","PEB 1202 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock02_53 ","PEB 1203 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock02_54 ","PEB 1204 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock02_55 ","PEB 1205 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock02_56 ","PEB 1206 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock02_57 ","PEB 1207 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock02_58 ","PEB 1208 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock02_59 ","PEB 1209 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock02_60 ","PEB 1210 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock02_61 ","PEB 1211 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock03_62 ","PEB 1212 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock03_63 ","PEB 1213 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock03_64 ","PEB 1214 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock03_65 ","PEB 1215 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock03_66 ","PEB 1216 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock03_67 ","PEB 1217 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock03_68 ","PEB 1218 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock03_69 ","PEB 1219 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock03_70 ","PEB 1220 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock03_71 ","PEB 1221 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock03_72 ","PEB 1222 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock03_73 ","PEB 1223 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock03_74 ","PEB 1224 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock03_75 ","PEB 1225 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock03_76 ","PEB 1226 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock03_77 ","PEB 1227 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock03_78 ","PEB 1228 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock03_79 ","PEB 1229 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock03_80 ","PEB 1230 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock03_81 ","PEB 1231 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock03_82 ","PEB 1232 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock03_83 ","PEB 1233 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock03_84 ","PEB 1234 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_INBlock03_85 ","PEB 1235 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_OUTBlock01_0 ","PAB 1150 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_OUTBlock01_1 ","PAB 1151 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_OUTBlock01_10 ","PAB 1160 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_OUTBlock01_11 ","PAB 1161 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_OUTBlock01_12 ","PAB 1162 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_OUTBlock01_13 ","PAB 1163 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_OUTBlock01_14 ","PAB 1164 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_OUTBlock01_15 ","PAB 1165 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_OUTBlock01_16 ","PAB 1166 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_OUTBlock01_17 ","PAB 1167 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_OUTBlock01_18 ","PAB 1168 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_OUTBlock01_19 ","PAB 1169 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_OUTBlock01_2 ","PAB 1152 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_OUTBlock01_20 ","PAB 1170 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_OUTBlock01_21 ","PAB 1171 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_OUTBlock01_22 ","PAB 1172 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_OUTBlock01_23 ","PAB 1173 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_OUTBlock01_24 ","PAB 1174 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_OUTBlock01_25 ","PAB 1175 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_OUTBlock01_26 ","PAB 1176 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_OUTBlock01_27 ","PAB 1177 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_OUTBlock01_3 ","PAB 1153 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_OUTBlock01_4 ","PAB 1154 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_OUTBlock01_5 ","PAB 1155 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_OUTBlock01_6 ","PAB 1156 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_OUTBlock01_7 ","PAB 1157 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_OUTBlock01_8 ","PAB 1158 ","BYTE ","MIX - Profibus Variables " +"gPAmPDS_OUTBlock01_9 ","PAB 1159 ","BYTE ","MIX - Profibus Variables " +"gPPM303_VFC_ActualValue ","PEW 1642 ","WORD ","MIX - Product Pump - Profibus Variables " +"gPPM303_VFC_ControlWord ","PAW 1640 ","WORD ","MIX - Product Pump - Profibus Variables " +"gPPM303_VFC_Refvalue ","PAW 1642 ","WORD ","MIX - Product Pump - Profibus Variables " +"gPPM303_VFC_StatusWord ","PEW 1640 ","WORD ","MIX - Product Pump - Profibus Variables " +"gPPM306_VFC_ActualValue ","PEW 1928 ","WORD ","MIX - Recirculation Pump - Profibus Variables " +"gPPM306_VFC_ControlWord ","PAW 1926 ","WORD ","MIX - Recirculation Pump - Profibus Variables " +"gPPM306_VFC_RefValue ","PAW 1928 ","WORD ","MIX - Recirculation Pump - Profibus Variables " +"gPPM306_VFC_StatusWord ","PEW 1926 ","WORD ","MIX - Recirculation Pump - Profibus Variables " +"gPPM307_VFC_ActualValue ","PEW 1662 ","WORD ","MIX - Booster Pump - Profibus Variables " +"gPPM307_VFC_ControlWord ","PAW 1660 ","WORD ","MIX - Booster Pump - Profibus Variables " +"gPPM307_VFC_Refvalue ","PAW 1662 ","WORD ","MIX - Booster Pump - Profibus Variables " +"gPPM307_VFC_StatusWord ","PEW 1660 ","WORD ","MIX - Booster Pump - Profibus Variables " +"gPPN301_VFC_ActualValue ","PEW 1602 ","WORD ","MIX - Water Pump - Profibus Variables " +"gPPN301_VFC_ControlWord ","PAW 1600 ","WORD ","MIX - Product Pump - Profibus Variables " +"gPPN301_VFC_Refvalue ","PAW 1602 ","WORD ","MIX - Product Pump - Profibus Variables " +"gPPN301_VFC_StatusWord ","PEW 1600 ","WORD ","MIX - Water Pump - Profibus Variables " +"gPPN304_VFC_ActualValue ","PEW 1682 ","WORD ","MIX - Vacuum Pump - Profibus Variables " +"gPPN304_VFC_ControlWord ","PAW 1680 ","WORD ","MIX - Vacuum Pump - Profibus Variables " +"gPPN304_VFC_Refvalue ","PAW 1682 ","WORD ","MIX - Vacuum Pump - Profibus Variables " +"gPPN304_VFC_StatusWord ","PEW 1680 ","WORD ","MIX - Vacuum Pump - Profibus Variables " +"gPPP302_VFC_ActualValue ","PEW 1622 ","WORD ","MIX - Syrup Pump - Profibus Variables " +"gPPP302_VFC_ControlWord ","PAW 1620 ","WORD ","MIX - Product Pump - Profibus Variables " +"gPPP302_VFC_Refvalue ","PAW 1622 ","WORD ","MIX - Product Pump - Profibus Variables " +"gPPP302_VFC_StatusWord ","PEW 1620 ","WORD ","MIX - Syrup Pump - Profibus Variables " +"gPRD_DeairTank_MaxLevel ","M 1737.5 ","BOOL ","MIX - LEVEL - Maximum Deaireator Tank Level during PROD " +"gPrdTank_Empty ","M 1734.5 ","BOOL ","MIX - LEVEL - Product Tank Empty " +"gPrdTank_Press_Fault ","M 1812.2 ","BOOL ","MIX - Product Tank Pressure Fault - Fault Alarm " +"gPress_Time_Sel ","M 300.5 ","BOOL "," " +"gProdAvailable ","M 1723.2 ","BOOL ","MIX - " +"gProdMeterTransferRecipe","M 1740.2 ","BOOL ","MIX - ProdAnalizer - Signal to the Product Meter Recipe Transfer " +"gProductChillerEn ","M 1714.1 ","BOOL ","MIX - " +"gProductionONS ","M 1720.7 ","BOOL ","MIX - " +"gProductMeterOK ","M 2001.1 ","BOOL ","MIX - BlenderCtrl__Main " +"gProductMFMResetTot ","M 1742.3 ","BOOL ","MIX - " +"gProductVFM_EPD ","M 1811.2 ","BOOL ","MIX - Fault Alarm " +"gProductVFM_Fault ","M 1811.6 ","BOOL ","MIX - Fault Alarm " +"gPTM304_HighLimit ","M 1738.3 ","BOOL ","MIX - Product Tank Pressure High Limit - PRESSURE - " +"gPTM304_OkToBlend ","M 1738.5 ","BOOL ","MIX - Product Tank Pressure OK To Blend - PRESSURE - " +"gPTN301_LowPressure ","M 1818.4 ","BOOL ","MIX - Supplies Alarm " +"gPTN301_NoPressure ","M 1818.3 ","BOOL ","MIX - Supplies Alarm " +"gPTN313_Acheaved ","M 1715.4 ","BOOL ","MIX - Vacuum Acheaved - still product signal from vacuum probe " +"gPV_SyrBrixOk ","M 1739.5 ","BOOL ","MIX - Syrup Brix value not Zero " +"gPV_SyrDensOk ","M 1739.6 ","BOOL ","MIX - Syrup Density value not zero " +"gQTM301_TooHigh ","M 1813.3 ","BOOL ","MIX - Product Brix Too High - Fault Alarm " +"gQTM301_TooLow ","M 1813.4 ","BOOL ","MIX - Product Brix Too Low - Fault Alarm " +"gQTM302_TooHigh ","M 1813.5 ","BOOL ","MIX - Product CO2 Too High - Fault Alarm " +"gQTM302_TooLow ","M 1813.6 ","BOOL ","MIX - Product CO2 Too Low - Fault Alarm " +"gRecipeManagement_Busy ","M 1715.7 ","BOOL ","MIX - Recipe Management Busy " +"gRecoveryEQPress ","M 1743.0 ","BOOL ","MIX - Recovery EquilPressure after drain " +"gRinseClose_RMM301 ","M 1737.0 ","BOOL ","MIX - RINSE - " +"gRinseClose_RMP302 ","M 1737.1 ","BOOL ","MIX - RINSE - " +"gRinseDrainRunning ","M 1737.2 ","BOOL ","MIX - RINSE - " +"gRinseONS ","M 1721.1 ","BOOL ","MIX - " +"gRMM301_PIDCtrlOk ","M 1740.7 ","BOOL ","MIX - Water PID Controlling Ok " +"gRMM301_Pos_Fault ","M 1811.7 ","BOOL ","MIX - Water Valve Position Fault - Fault Alarm " +"gRMM303_PIDCtrlOk ","M 1741.1 ","BOOL ","MIX - CO2 PID Controlling Ok " +"gRMM303_Pos_Fault ","M 1812.1 ","BOOL ","MIX - Carbo CO2 Valve Position - Fault Alarm " +"gRMM304_PIDCtrlOk ","M 1742.7 ","BOOL ","MIX - GAS2 PID Controlling Ok " +"gRMM304_Pos_Fault ","M 1810.6 ","BOOL ","MIX - GAS2 Valve Position - Fault Alarm " +"gRMP302_PIDCtrlOk ","M 1741.0 ","BOOL ","MIX - Syrup PID Controlling Ok " +"gRMP302_Pos_Fault ","M 1812.0 ","BOOL ","MIX - Syrup Valve Position Fault - Fault Alarm " +"gRMP302PrepEnable ","M 301.1 ","BOOL ","Syrup Preparation Enable RMP302 " +"gRSTBYPSGlicoleSys ","T 94 ","TIMER ","MIX - " +"gRVM301_Fault ","M 1816.3 ","BOOL ","MIX - Product Tank Pressure Valve Fault - Fault Alarm " +"gRVN304_Fault ","M 1816.4 ","BOOL ","MIX - Deaireation Valve Fault - Fault Alarm " +"gSP_AVM308 ","M 1300.4 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM309 ","M 1300.5 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM311 ","M 1300.6 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM313 ","M 1300.7 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM314 ","M 1302.6 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM315 ","M 1301.0 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM317 ","M 1301.1 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM321 ","M 1301.2 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM322 ","M 1301.3 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM324 ","M 1301.4 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM326 ","M 1301.6 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM327 ","M 1301.7 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM328 ","M 1302.0 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM329 ","M 1302.1 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM331 ","M 1302.3 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM341 ","M 1302.4 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM342 ","M 1302.5 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM345 ","M 1303.0 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM346 ","M 1303.1 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM348 ","M 1303.3 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM349 ","M 1303.4 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM362 ","M 1303.6 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM363 ","M 1302.7 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM369 ","M 1305.3 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM370 ","M 1305.4 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM378 ","M 1305.5 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM379 ","M 1305.6 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM381 ","M 1303.7 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM385 ","M 1304.0 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM386 ","M 1304.1 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM387 ","M 1304.2 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM388 ","M 1304.3 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM389 ","M 1304.4 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM390 ","M 1304.5 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM391 ","M 1304.6 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM393 ","M 1313.4 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM394 ","M 1313.5 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM396 ","M 1305.0 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM397 ","M 1305.1 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVM398 ","M 1305.2 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVN301 ","M 1306.0 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVN302 ","M 1306.1 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVN303 ","M 1306.2 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVN305 ","M 1306.4 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVN306 ","M 1306.5 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVN30x_En ","M 1320.0 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVN314 ","M 1307.0 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVN318 ","M 1307.2 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVN323 ","M 1307.3 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVN324 ","M 1307.4 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVN327 ","M 1307.7 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVN328 ","M 1308.0 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVN329 ","M 1308.1 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVN370 ","M 1308.2 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVN371 ","M 1308.3 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVN373 ","M 1308.5 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVN395 ","M 1308.6 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVP315 ","M 1309.2 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVP316 ","M 1309.3 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVP317 ","M 1309.4 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVP320 ","M 1322.7 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVP324 ","M 1309.7 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVP344 ","M 1310.0 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVP361 ","M 1310.2 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVP363 ","M 1310.4 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVP364 ","M 1310.5 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVP365 ","M 1310.6 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVP388 ","M 1311.7 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVS301 ","M 1312.0 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVS302 ","M 1312.1 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVS341 ","M 1312.4 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVS342 ","M 1312.5 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVS343 ","M 1312.6 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVS344 ","M 1312.7 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVS345 ","M 1313.0 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVS346 ","M 1313.1 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVS347 ","M 1313.2 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVS348 ","M 1313.3 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_AVS399 ","M 1313.6 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_BldTankPress_En ","M 1315.0 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_CarboPipe_En ","M 1315.1 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_CIP_CO2_Inj ","M 1315.2 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_CIP_Temp_En ","M 1315.3 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_CIPChangeSanitize ","M 1315.4 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_CIPDrainRequest ","M 1315.5 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_CIPHotPhase ","M 1315.6 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_CIPRunning ","M 1315.7 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_DSConcBooking ","M 1322.1 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_DSDosingCompleted ","M 1322.3 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_DSFlushBooking ","M 1322.2 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_DSFlushCompleted ","M 1322.4 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_DSSendEnable ","M 1322.5 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_DSVolumeBooking ","M 1322.0 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_FillerFlushingRun ","M 1320.7 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_FillerProdLoad ","M 1316.1 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_FillerProdSend ","M 1316.2 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_HotWaterRequest ","M 1316.3 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_LimitCO2PressValve ","M 1316.4 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_LoadingLevel ","M 1322.6 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_PID_Head_Enable ","M 1316.5 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_PID_Head_Manual ","M 1316.6 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_PPM303 ","M 1318.4 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_PPM307 ","M 1318.5 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_PPM309 ","M 1318.6 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_PPN301 ","M 1318.0 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_PPN304 ","M 1318.1 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_PPN305 ","M 1318.2 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_PPP302 ","M 1318.3 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_QTM305X ","M 1323.0 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_QTM305Y ","M 1323.1 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_RVM302_En ","M 1320.5 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_RVM302_Fixed ","M 1320.6 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_RVN302_En ","M 1320.1 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_RVN302_Fixed ","M 1320.2 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_RVP303_En ","M 1320.3 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_SendSodaRequest ","M 1316.7 ","BOOL ","MIX - Procedure auxiliary variable " +"gSP_SyrRoomPump ","M 1317.0 ","BOOL ","MIX - Procedure auxiliary variable " +"gStartUpToFiller_FP ","M 304.1 ","BOOL ","Start Up Ready to Filler " +"gStopBlendCarboError ","M 1714.6 ","BOOL ","MIX - RUN CTRL - Water Speed to stop Blend error and Carbo error " +"gSyrBrixOutSpec_Fault ","M 1817.5 ","BOOL ","MIX - Supplies Alarm " +"gSyrLevelTank_Ack ","M 300.7 ","BOOL ","Syrup Prepartion: syrup tank ack " +"gSyrLevelTank_Ack_AUX ","M 301.0 ","BOOL ","Syrup Preparation : syrup tank ack AUX " +"gSyrRoomPump_Fault ","M 1817.4 ","BOOL ","MIX - Supplies Alarm " +"gSyrTank_HighLvl_Fault ","M 1816.5 ","BOOL ","MIX - Syrup Tank High Level Fault - Fault Alarm " +"gSyrTank_Loading ","M 1723.5 ","BOOL ","MIX - Syrup Tank Loading " +"gSyrTank_LowLvl_Fault ","M 1817.3 ","BOOL ","MIX - Syrup Tank Low Level Fault - Supplies Alarm " +"gSyrupDensOutSpec_Fault ","M 1817.6 ","BOOL ","MIX - Supplies Alarm " +"gSyrupLineNotSelected ","M 1820.2 ","BOOL ","MIX - Operator Alarm " +"gSyrupMeterOK ","M 2001.3 ","BOOL ","MIX - mPDS_SYR_PA_Control " +"gSyrupQcoRinse ","M 1743.1 ","BOOL ","MIX - Syrup Line QCO Rinse " +"gSyrupRoomEn ","M 1714.3 ","BOOL ","MIX - " +"gSystem_Time ","MD 1990 ","TIME ","MIX - System time " +"gTN301CloseAVS345 ","M 1500.1 ","BOOL ","MIX - Max Level TN301 Close AVS345 " +"gTotalDrainFlushAct ","M 1743.2 ","BOOL "," " +"gTrackFillerSpeed ","M 1723.3 ","BOOL ","MIX - " +"gTTM306_H2O_TempTooHigh ","M 1817.7 ","BOOL ","MIX - Water Temperature Too High - Supplies Alarm " +"gTTM306_PRD_TempTooHigh ","M 1818.0 ","BOOL ","MIX - Product Temperature Too High - Supplies Alarm " +"gUVLampHighTemperature ","M 1715.6 ","BOOL ","MIX - " +"gVacuumCtrlAlarmTemp ","M 1716.6 ","BOOL ","MIX - Vacuum Ctrl - Temperature " +"gVacuumCtrlCoolingON ","M 1716.4 ","BOOL ","MIX - Vacuum Ctrl - Temperature " +"gVacuumCtrlWarningTemp ","M 1716.5 ","BOOL ","MIX - Vacuum Ctrl - Temperature " +"gVacuumEn ","M 1715.1 ","BOOL ","MIX - Deox Vacuum Enable " +"gVacuumReq ","M 1716.2 ","BOOL ","MIX - Deox Vacuum System Request " +"gVacuumTimeOut_Fault ","M 1812.3 ","BOOL ","MIX - Fault Alarm " +"gWaitLevToHold_TankPress","M 1738.6 ","BOOL ","MIX - PRESSURE - " +"gWarningHorn ","M 1713.7 ","BOOL ","MIX - SAFETIES " +"gWaterChillerEn ","M 1714.2 ","BOOL ","MIX - " +"gWorkshopTest ","M 1714.4 ","BOOL ","MIX - Use during Workshop test - BF - " +"HMI CPU_DP Diag ","DB 174 ","DB 174 ","CPU and Profibus detailed diagnostic " +"HMI Drive ","DB 1020 ","DB 1020 ","Frequency Converter Parameter " +"HMI_Alarms ","DB 1000 ","DB 1000 ","MIX - HMI Alarms " +"HMI_Blender_Parameters ","DB 1001 ","DB 1001 ","MIX - HMI Blender Parameters " +"HMI_Device ","DB 1010 ","DB 1010 ","MIX - HMI " +"HMI_Device_AVS ","DB 1015 ","DB 1015 ","MIX - HMI " +"HMI_Digital ","DB 1012 ","DB 1012 ","MIX - HMI " +"HMI_ICS ","DB 1014 ","DB 1014 ","MIX - HMI ICS Dosing Station " +"HMI_ICS_Status ","DB 1016 ","DB 1016 ","MIX - HMI ICS Status " +"HMI_Instrument ","DB 1011 ","DB 1011 ","MIX - HMI " +"HMI_IO_Showing ","DB 1002 ","DB 1002 ","MIX - HMI_IO_Showing " +"HMI_Local_CIP_Variables ","DB 1003 ","DB 1003 ","MIX - HMI Local CIP Variables " +"HMI_PID ","DB 1013 ","DB 1013 ","MIX - HMI " +"HMI_PID.RVM319.Error ","MD 1944 ","REAL ","MIX - " +"HMI_Recipe_Edit ","DB 1008 ","DB 1008 ","MIX - Recipe Data Edit " +"HMI_Recipe_Name ","DB 1009 ","DB 1009 "," " +"HMI_Service ","DB 1004 ","DB 1004 ","MIX - HMI Service " +"HMI_Totalizers ","DB 1017 ","DB 1017 ","HMI Consumption Totalizers " +"HMI_Variables_Cmd ","DB 1005 ","DB 1005 ","MIX - HMI Variables Cmd " +"HMI_Variables_Status ","DB 1006 ","DB 1006 ","MIX - HMI Variables Status " +"I/O_FLT1 ","OB 82 ","OB 82 ","I/O Point Fault 1 " +"I_STRNG ","FC 16 ","FC 16 ","Integer to String " +"ICS Hndsk receive signal","UDT 905 ","UDT 905 ","MIX - ICS receive signal dosing station " +"ICS Hndsk send signal ","UDT 906 ","UDT 906 ","MIX - ICS send signal dosing station " +"ICS Profibus Comm ","FB 200 ","FB 200 ","MIX - Dosing station profibus communication " +"ICSStatusScroll_Time ","T 179 ","TIMER ","MIX - ICSStatusScroll " +"Input ","FB 1718 ","FB 1718 ","MIX - Input " +"Input_CheckFlowMetersSta","FC 1718 ","FC 1718 ","MIX - Check Flow Meters State " +"Input_Data ","DB 940 ","FB 1718 ","MIX - Input Data " +"Input_DigitalCtrl ","FC 1720 ","FC 1720 ","MIX - Digital input control " +"Input_DigitalScanner ","FC 1719 ","FC 1719 ","MIX - Digital input scanner " +"Instrument_Scanner ","FC 1968 ","FC 1968 "," " +"Integral ","FB 1700 ","FB 1700 ","MIX - Integral " +"Interlocking NET ","FB 1991 ","FB 1991 ","MIX - Interlocking NET " +"Interlocking_NET ","DB 991 ","FB 1991 ","MIX - Interlocking NET Data " +"Interlocking_Panel ","FC 1999 ","FC 1999 ","MIX - Interlocking Panel " +"Interlocking_Variables ","DB 900 ","DB 900 ","MIX - Interlocking_Variables " +"Interrupt ","OB 40 ","OB 40 ","Interrupt Management " +"ISOonTCP_or_TCP_Protocol","FB 1 ","FB 1 ","ETH "Iso on TCP" or "Native TCP" com block for PN CPU (V2.2) " +"LastbottleDone ","M 900.0 ","BOOL ","Laste Botte filled done " +"LGC_GADR ","SFC 49 ","SFC 49 ","Query the Module Slot Belonging to a Logical Address " +"LIMIT ","FC 22 ","FC 22 ","Limit " +"LIMIT_I ","FC 1849 ","FC 1849 ","MIX - Limits the value range for REALS " +"LIMIT_R ","FC 1848 ","FC 1848 ","MIX - Limits the value range for REALS " +"LowPassFilter ","FB 1701 ","FB 1701 ","MIX - Low_Pass_Filter " +"m_PAmPDS_RecipeTmr ","T 164 ","TIMER ","MIX - mPDS_PA_Ctrl_Write " +"m_PAmPDS_SYR_RecipeTmr ","T 1 ","TIMER ","MIX - mPDS_SYR_PA_Ctrl_Write " +"m_StartRecircPHE ","T 166 ","TIMER ","MIX - BlenderRun__Control " +"m_StopRecircPHE ","T 165 ","TIMER ","MIX - BlenderRun__Control " +"M_validat_27_01_25 ","M 800.0 ","BOOL ","Modification Validation 27_01_2025 " +"M100.0 ","M 100.0 ","BOOL ","QCO Started " +"M100.1 ","M 100.1 ","BOOL "," " +"M100.2 ","M 100.2 ","BOOL ","QCO Rinse completed " +"M100.3 ","M 100.3 ","BOOL ","QCO Filler flushing FP " +"M100.4 ","M 100.4 ","BOOL "," " +"M100.5 ","M 100.5 ","BOOL "," " +"M100.6 ","M 100.6 ","BOOL ","QCO Ended " +"M100.7 ","M 100.7 ","BOOL "," " +"M110.0 ","M 110.0 ","BOOL ","DEOX Recovery completed " +"M1530 ","M 153.0 ","BOOL ","FN FTN390 Counter - Water Inlet " +"M1531 ","M 153.1 ","BOOL ","FN FTN390 Partial Counter Reset - Water Inlet " +"M1532 ","M 153.2 ","BOOL ","FN FTG301 Counter - Compressed Air Inlet " +"M1533 ","M 153.3 ","BOOL ","FN FTG301 Partial Counter Reset - Compressed Air Inlet " +"M1543 ","M 154.3 ","BOOL ","FN FTM307 Partial Counter Reset-N2 Inlet " +"M1544 ","M 154.4 ","BOOL ","FN FTG302 Counter - CO2 Inlet " +"M1545 ","M 154.5 ","BOOL ","FN FTG302 Partial Counter Reset - CO2 Inlet " +"M1546 ","M 154.6 ","BOOL ","FP FTN390 CIP Counter - Water Inlet " +"M1547 ","M 154.7 ","BOOL ","FP FTN390 RINSE Counter - Water Inlet " +"M1550 ","M 155.0 ","BOOL ","FP FTN390 PRODUCTION Counter - Water Inlet " +"M1551 ","M 155.1 ","BOOL ","FP FTG301 RINSE Counter - Compressed Air Inlet " +"M1552 ","M 155.2 ","BOOL ","FP FTG301 PRODUCTION Counter - Compressed Air Inlet " +"M1553 ","M 155.3 ","BOOL ","FP FTG302 CIP Counter - CO2 Inlet " +"M1554 ","M 155.4 ","BOOL ","FP FTM307 Counter-N2 Inlet " +"M1556 ","M 155.6 ","BOOL ","FN Power Meter Counter - KWH " +"M1557 ","M 155.7 ","BOOL ","FN Power Meter Partial Counter Reset - KWH " +"M1560 ","M 156.0 ","BOOL ","FP Power Meter RINSE Counter - KWH " +"M1561 ","M 156.1 ","BOOL ","FP Power Meter CIP Counter - KWH " +"M1562 ","M 156.2 ","BOOL ","FN Power Meter Counter - KVARH " +"M1563 ","M 156.3 ","BOOL ","FN Power Meter Partial Counter Reset - KVARH " +"M1564 ","M 156.4 ","BOOL ","FP Power Meter RINSE Counter - KVARH " +"M1565 ","M 156.5 ","BOOL ","FP Power Meter CIP Counter - KVARH " +"m1570 ","M 157.0 ","BOOL ","QTM305 Mautent Position " +"m1571 ","M 157.1 ","BOOL ","QTM305 Measuring Position " +"M1604.2 ","M 1604.2 ","BOOL ","PPN301 Stop delay " +"M1738.0 ","M 1738.0 ","BOOL ","Density OK Diet recipe > 300 Lt " +"M1738.4 ","M 1738.4 ","BOOL ","PressureOkForDraining " +"M1743.5 ","M 1743.5 ","BOOL ","Production With Syrup Low Brix (<10 Brix) " +"M18100 ","M 1810.0 ","BOOL ","MIX - Fault Alarm " +"M18101 ","M 1810.1 ","BOOL ","MIX - Fault Alarm " +"M18102 ","M 1810.2 ","BOOL ","MIX - Fault Alarm " +"M18103 ","M 1810.3 ","BOOL ","MIX - Fault Alarm " +"M18104 ","M 1810.4 ","BOOL ","MIX - Fault Alarm " +"M18186 ","M 1818.6 ","BOOL ","MIX - Supplies Alarm " +"M18187 ","M 1818.7 ","BOOL ","MIX - Supplies Alarm " +"M18190 ","M 1819.0 ","BOOL ","MIX - Supplies Alarm " +"M18191 ","M 1819.1 ","BOOL ","MIX - Supplies Alarm " +"M18192 ","M 1819.2 ","BOOL ","MIX - Supplies Alarm " +"M18193 ","M 1819.3 ","BOOL ","MIX - Supplies Alarm " +"M18194 ","M 1819.4 ","BOOL ","MIX - Supplies Alarm " +"M18195 ","M 1819.5 ","BOOL ","MIX - Supplies Alarm " +"M18196 ","M 1819.6 ","BOOL ","MIX - Supplies Alarm " +"M18197 ","M 1819.7 ","BOOL ","MIX - Supplies Alarm " +"M18203 ","M 1820.3 ","BOOL ","MIX - Operator Alarm " +"M18204 ","M 1820.4 ","BOOL ","MIX - Operator Alarm " +"M18205 ","M 1820.5 ","BOOL ","MIX - Operator Alarm " +"M18206 ","M 1820.6 ","BOOL ","MIX - Operator Alarm " +"M18207 ","M 1820.7 ","BOOL ","MIX - Operator Alarm " +"M18210 ","M 1821.0 ","BOOL ","MIX - Operator Alarm " +"M18211 ","M 1821.1 ","BOOL ","MIX - Operator Alarm " +"M18212 ","M 1821.2 ","BOOL ","MIX - Operator Alarm " +"M18213 ","M 1821.3 ","BOOL ","MIX - Operator Alarm " +"M18214 ","M 1821.4 ","BOOL ","MIX - Operator Alarm " +"M18215 ","M 1821.5 ","BOOL ","MIX - Operator Alarm " +"M18216 ","M 1821.6 ","BOOL ","MIX - Operator Alarm " +"M18217 ","M 1821.7 ","BOOL ","MIX - Operator Alarm " +"M18700 ","M 1870.0 ","BOOL ","MIX - Profibus network Fault - Node01 " +"M18701 ","M 1870.1 ","BOOL ","MIX - Profibus network Fault - Node02 " +"M18702 ","M 1870.2 ","BOOL ","MIX - Profibus network Fault - Node03 " +"M18703 ","M 1870.3 ","BOOL ","MIX - Profibus network Fault - Node04 " +"M18704 ","M 1870.4 ","BOOL ","MIX - Profibus network Fault - Node05 " +"M18705 ","M 1870.5 ","BOOL ","MIX - Profibus network Fault - Node06 " +"M18706 ","M 1870.6 ","BOOL ","MIX - Profibus network Fault - Node07 " +"M18707 ","M 1870.7 ","BOOL ","MIX - Profibus network Fault - Node08 " +"M18710 ","M 1871.0 ","BOOL ","MIX - Profibus network Fault - Node09 " +"M18711 ","M 1871.1 ","BOOL ","MIX - Profibus network Fault - Node10 " +"M18712 ","M 1871.2 ","BOOL ","MIX - Profibus network Fault - Node11 " +"M18713 ","M 1871.3 ","BOOL ","MIX - Profibus network Fault - Node12 " +"M18714 ","M 1871.4 ","BOOL ","MIX - Profibus network Fault - Node13 " +"M18715 ","M 1871.5 ","BOOL ","MIX - Profibus network Fault - Node14 " +"M18716 ","M 1871.6 ","BOOL ","MIX - Profibus network Fault - Node15 " +"M18717 ","M 1871.7 ","BOOL ","MIX - Profibus network Fault - Node16 " +"M18720 ","M 1872.0 ","BOOL ","MIX - Profibus network Fault - Node17 " +"M18721 ","M 1872.1 ","BOOL ","MIX - Profibus network Fault - Node18 " +"M18722 ","M 1872.2 ","BOOL ","MIX - Profibus network Fault - Node19 " +"M18723 ","M 1872.3 ","BOOL ","MIX - Profibus network Fault - Node20 " +"M18724 ","M 1872.4 ","BOOL ","MIX - Profibus network Fault - Node21 " +"M18725 ","M 1872.5 ","BOOL ","MIX - Profibus network Fault - Node22 " +"M18726 ","M 1872.6 ","BOOL ","MIX - Profibus network Fault - Node23 " +"M18727 ","M 1872.7 ","BOOL ","MIX - Profibus network Fault - Node24 " +"M18730 ","M 1873.0 ","BOOL ","MIX - Profibus network Fault - Node25 " +"M18731 ","M 1873.1 ","BOOL ","MIX - Profibus network Fault - Node26 " +"M18732 ","M 1873.2 ","BOOL ","MIX - Profibus network Fault - Node27 " +"M18733 ","M 1873.3 ","BOOL ","MIX - Profibus network Fault - Node28 " +"M18734 ","M 1873.4 ","BOOL ","MIX - Profibus network Fault - Node29 " +"M18735 ","M 1873.5 ","BOOL ","MIX - Profibus network Fault - Node30 " +"M18736 ","M 1873.6 ","BOOL ","MIX - Profibus network Fault - Node31 " +"M18737 ","M 1873.7 ","BOOL ","MIX - Profibus network Fault - Node32 " +"M18740 ","M 1874.0 ","BOOL ","MIX - Profibus network Fault - Node33 " +"M18741 ","M 1874.1 ","BOOL ","MIX - Profibus network Fault - Node34 " +"M18742 ","M 1874.2 ","BOOL ","MIX - Profibus network Fault - Node35 " +"M18743 ","M 1874.3 ","BOOL ","MIX - Profibus network Fault - Node36 " +"M18744 ","M 1874.4 ","BOOL ","MIX - Profibus network Fault - Node37 " +"M18745 ","M 1874.5 ","BOOL ","MIX - Profibus network Fault - Node38 " +"M18746 ","M 1874.6 ","BOOL ","MIX - Profibus network Fault - Node39 " +"M18747 ","M 1874.7 ","BOOL ","MIX - Profibus network Fault - Node40 " +"M18750 ","M 1875.0 ","BOOL ","MIX - Profibus network Fault - Node41 " +"M18751 ","M 1875.1 ","BOOL ","MIX - Profibus network Fault - Node42 " +"M18752 ","M 1875.2 ","BOOL ","MIX - Profibus network Fault - Node43 " +"M18753 ","M 1875.3 ","BOOL ","MIX - Profibus network Fault - Node44 " +"M18754 ","M 1875.4 ","BOOL ","MIX - Profibus network Fault - Node45 " +"M18755 ","M 1875.5 ","BOOL ","MIX - Profibus network Fault - Node46 " +"M18756 ","M 1875.6 ","BOOL ","MIX - Profibus network Fault - Node47 " +"M18757 ","M 1875.7 ","BOOL ","MIX - Profibus network Fault - Node48 " +"M18760 ","M 1876.0 ","BOOL ","MIX - Profibus network Fault - Node49 " +"M18761 ","M 1876.1 ","BOOL ","MIX - Profibus network Fault - Node50 " +"M18762 ","M 1876.2 ","BOOL ","MIX - Profibus network Fault - Node51 " +"M18763 ","M 1876.3 ","BOOL ","MIX - Profibus network Fault - Node52 " +"M18764 ","M 1876.4 ","BOOL ","MIX - Profibus network Fault - Node53 " +"M18765 ","M 1876.5 ","BOOL ","MIX - Profibus network Fault - Node54 " +"M18766 ","M 1876.6 ","BOOL ","MIX - Profibus network Fault - Node55 " +"M18767 ","M 1876.7 ","BOOL ","MIX - Profibus network Fault - Node56 " +"M18770 ","M 1877.0 ","BOOL ","MIX - Profibus network Fault - Node57 " +"M18771 ","M 1877.1 ","BOOL ","MIX - Profibus network Fault - Node58 " +"M18772 ","M 1877.2 ","BOOL ","MIX - Profibus network Fault - Node59 " +"M18773 ","M 1877.3 ","BOOL ","MIX - Profibus network Fault - Node60 " +"M18774 ","M 1877.4 ","BOOL ","MIX - Profibus network Fault - Node61 " +"M18775 ","M 1877.5 ","BOOL ","MIX - Profibus network Fault - Node62 " +"M18776 ","M 1877.6 ","BOOL ","MIX - Profibus network Fault - Node63 " +"M18777 ","M 1877.7 ","BOOL ","MIX - Profibus network Fault - Node64 " +"M18780 ","M 1878.0 ","BOOL ","MIX - Profibus network Fault - Node65 " +"M18781 ","M 1878.1 ","BOOL ","MIX - Profibus network Fault - Node66 " +"M18782 ","M 1878.2 ","BOOL ","MIX - Profibus network Fault - Node67 " +"M18783 ","M 1878.3 ","BOOL ","MIX - Profibus network Fault - Node68 " +"M18784 ","M 1878.4 ","BOOL ","MIX - Profibus network Fault - Node69 " +"M18785 ","M 1878.5 ","BOOL ","MIX - Profibus network Fault - Node70 " +"M18786 ","M 1878.6 ","BOOL ","MIX - Profibus network Fault - Node71 " +"M18787 ","M 1878.7 ","BOOL ","MIX - Profibus network Fault - Node72 " +"M18790 ","M 1879.0 ","BOOL ","MIX - Profibus network Fault - Node73 " +"M18791 ","M 1879.1 ","BOOL ","MIX - Profibus network Fault - Node74 " +"M18792 ","M 1879.2 ","BOOL ","MIX - Profibus network Fault - Node75 " +"M18793 ","M 1879.3 ","BOOL ","MIX - Profibus network Fault - Node76 " +"M18794 ","M 1879.4 ","BOOL ","MIX - Profibus network Fault - Node77 " +"M18795 ","M 1879.5 ","BOOL ","MIX - Profibus network Fault - Node78 " +"M18796 ","M 1879.6 ","BOOL ","MIX - Profibus network Fault - Node79 " +"M18797 ","M 1879.7 ","BOOL ","MIX - Profibus network Fault - Node80 " +"M18800 ","M 1880.0 ","BOOL ","MIX - Profibus network Fault - Node81 " +"M18801 ","M 1880.1 ","BOOL ","MIX - Profibus network Fault - Node82 " +"M18802 ","M 1880.2 ","BOOL ","MIX - Profibus network Fault - Node83 " +"M18803 ","M 1880.3 ","BOOL ","MIX - Profibus network Fault - Node84 " +"M18804 ","M 1880.4 ","BOOL ","MIX - Profibus network Fault - Node85 " +"M18805 ","M 1880.5 ","BOOL ","MIX - Profibus network Fault - Node86 " +"M18806 ","M 1880.6 ","BOOL ","MIX - Profibus network Fault - Node87 " +"M18807 ","M 1880.7 ","BOOL ","MIX - Profibus network Fault - Node88 " +"M18810 ","M 1881.0 ","BOOL ","MIX - Profibus network Fault - Node89 " +"M18811 ","M 1881.1 ","BOOL ","MIX - Profibus network Fault - Node90 " +"M18812 ","M 1881.2 ","BOOL ","MIX - Profibus network Fault - Node91 " +"M18813 ","M 1881.3 ","BOOL ","MIX - Profibus network Fault - Node92 " +"M18814 ","M 1881.4 ","BOOL ","MIX - Profibus network Fault - Node93 " +"M18815 ","M 1881.5 ","BOOL ","MIX - Profibus network Fault - Node94 " +"M18816 ","M 1881.6 ","BOOL ","MIX - Profibus network Fault - Node95 " +"M18817 ","M 1881.7 ","BOOL ","MIX - Profibus network Fault - Node96 " +"M18820 ","M 1882.0 ","BOOL ","MIX - Profibus network Fault - Node97 " +"M18821 ","M 1882.1 ","BOOL ","MIX - Profibus network Fault - Node98 " +"M18822 ","M 1882.2 ","BOOL ","MIX - Profibus network Fault - Node99 " +"M18823 ","M 1882.3 ","BOOL ","MIX - Profibus network Fault - Node100 " +"M18824 ","M 1882.4 ","BOOL ","MIX - Profibus network Fault - Node101 " +"M18825 ","M 1882.5 ","BOOL ","MIX - Profibus network Fault - Node102 " +"M18826 ","M 1882.6 ","BOOL ","MIX - Profibus network Fault - Node103 " +"M18827 ","M 1882.7 ","BOOL ","MIX - Profibus network Fault - Node104 " +"M18830 ","M 1883.0 ","BOOL ","MIX - Profibus network Fault - Node105 " +"M18831 ","M 1883.1 ","BOOL ","MIX - Profibus network Fault - Node106 " +"M18832 ","M 1883.2 ","BOOL ","MIX - Profibus network Fault - Node107 " +"M18833 ","M 1883.3 ","BOOL ","MIX - Profibus network Fault - Node108 " +"M18834 ","M 1883.4 ","BOOL ","MIX - Profibus network Fault - Node109 " +"M18835 ","M 1883.5 ","BOOL ","MIX - Profibus network Fault - Node110 " +"M18836 ","M 1883.6 ","BOOL ","MIX - Profibus network Fault - Node111 " +"M18837 ","M 1883.7 ","BOOL ","MIX - Profibus network Fault - Node112 " +"M18840 ","M 1884.0 ","BOOL ","MIX - Profibus network Fault - Node113 " +"M18841 ","M 1884.1 ","BOOL ","MIX - Profibus network Fault - Node114 " +"M18842 ","M 1884.2 ","BOOL ","MIX - Profibus network Fault - Node115 " +"M18843 ","M 1884.3 ","BOOL ","MIX - Profibus network Fault - Node116 " +"M18844 ","M 1884.4 ","BOOL ","MIX - Profibus network Fault - Node117 " +"M18845 ","M 1884.5 ","BOOL ","MIX - Profibus network Fault - Node118 " +"M18846 ","M 1884.6 ","BOOL ","MIX - Profibus network Fault - Node119 " +"M18847 ","M 1884.7 ","BOOL ","MIX - Profibus network Fault - Node120 " +"M18850 ","M 1885.0 ","BOOL ","MIX - Profibus network Fault - Node121 " +"M18851 ","M 1885.1 ","BOOL ","MIX - Profibus network Fault - Node122 " +"M18852 ","M 1885.2 ","BOOL ","MIX - Profibus network Fault - Node123 " +"M18853 ","M 1885.3 ","BOOL ","MIX - Profibus network Fault - Node124 " +"M18854 ","M 1885.4 ","BOOL ","MIX - Profibus network Fault - Node125 " +"M18855 ","M 1885.5 ","BOOL ","MIX - Profibus network Fault - Node126 " +"M18856 ","M 1885.6 ","BOOL ","MIX - Profibus network Fault - Node127 " +"M18857 ","M 1885.7 ","BOOL ","MIX - Profibus network Fault - Node128 " +"M19000 ","M 1900.0 ","BOOL ","MIX - EmergencyPressONS " +"M19001 ","M 1900.1 ","BOOL ","MIX - Prod FP " +"M19002 ","M 1900.2 ","BOOL ","MIX - Rinse FP " +"M19003 ","M 1900.3 ","BOOL ","MIX - CIP FP " +"M19007 ","M 1900.7 ","BOOL ","MIX - Start FP " +"M19010 ","M 1901.0 ","BOOL ","MIX - StartPump FP " +"M19011 ","M 1901.1 ","BOOL ","MIX - All Auto FP " +"M19012 ","M 1901.2 ","BOOL ","MIX - 1Min FP " +"M19013 ","M 1901.3 ","BOOL ","MIX - Stop FP " +"M19014 ","M 1901.4 ","BOOL ","MIX - ColdReq FP " +"M19015 ","M 1901.5 ","BOOL ","MIX - WarmReq FP " +"M19016 ","M 1901.6 ","BOOL ","MIX - ColdDone FP " +"M19017 ","M 1901.7 ","BOOL ","MIX - WarmDone FP " +"M19020 ","M 1902.0 ","BOOL ","MIX - FillerRinseDone FP " +"M19021 ","M 1902.1 ","BOOL ","MIX - RinseDone FP " +"M19022 ","M 1902.2 ","BOOL ","MIX - CIPChange FP " +"M19023 ","M 1902.3 ","BOOL ","MIX - RinseRunning FP " +"M19024 ","M 1902.4 ","BOOL ","MIX - PressREQ FP " +"M19025 ","M 1902.5 ","BOOL ","MIX - F1_StartStop_Command FP " +"M19026 ","M 1902.6 ","BOOL ","MIX - PressLatch FP " +"M19027 ","M 1902.7 ","BOOL ","MIX - ProdBrixHigh FP " +"M19030 ","M 1903.0 ","BOOL ","MIX - ProdBrixLow FP " +"M19031 ","M 1903.1 ","BOOL ","MIX - ProdCO2High FP " +"M19032 ","M 1903.2 ","BOOL ","MIX - ProdCO2Low FP " +"M19033 ","M 1903.3 ","BOOL ","MIX - WaterHighTemp FP " +"M19034 ","M 1903.4 ","BOOL ","MIX - ProdHighTemp FP " +"M19060 ","M 1906.0 ","BOOL ","MIX - CIPTotalDrainREQ FP " +"M19061 ","M 1906.1 ","BOOL ","MIX - CIPDrainEnd FP " +"M19062 ","M 1906.2 ","BOOL ","MIX - ProdTankFloodREQ FP " +"M19063 ","M 1906.3 ","BOOL ","MIX - SyrTankFloodREQ FP " +"M19064 ","M 1906.4 ","BOOL ","MIX - DeairTankFloodREQ FP " +"M19074 ","M 1907.4 ","BOOL ","MIX - FP ****** " +"M19075 ","M 1907.5 ","BOOL ","MIX - FP ****** " +"M19076 ","M 1907.6 ","BOOL ","MIX - Product tank Drain Manual Reset FP " +"M19077 ","M 1907.7 ","BOOL ","MIX - Product tank Drain Manual Reset ONS " +"M19080 ","M 1908.0 ","BOOL ","MIX - Totalize FN " +"M19083 ","M 1908.3 ","BOOL ","MIX - Load Next Recipe ONS " +"M19084 ","M 1908.4 ","BOOL ","MIX - Load Next Recipe FP " +"M19086 ","M 1908.6 ","BOOL ","MIX - FP ****** " +"M19091 ","M 1909.1 ","BOOL ","MIX - FP ****** " +"M19094 ","M 1909.4 ","BOOL ","MIX - FP ****** " +"M19095 ","M 1909.5 ","BOOL ","MIX - FP ****** " +"M19100 ","M 1910.0 ","BOOL ","MIX - Interlocking Net : SYRUP Room Communication Error " +"M19101 ","M 1910.1 ","BOOL ","MIX - Interlocking Net : CIP Communication Error " +"M19102 ","M 1910.2 ","BOOL ","MIX - Interlocking Net : PAST Communication Error " +"M19103 ","M 1910.3 ","BOOL ","MIX - Interlocking Net : FILLER Communication Error " +"M19104 ","M 1910.4 ","BOOL ","MIX - Interlocking Net : PRODUCT Room Communication Error " +"M19105 ","M 1910.5 ","BOOL ","MIX - Interlocking Net : XX Communication Error " +"M19106 ","M 1910.6 ","BOOL ","MIX - Interlocking Net : XX Communication Error " +"M19107 ","M 1910.7 ","BOOL ","MIX - Interlocking Net : XX Communication Error " +"M19110 ","M 1911.0 ","BOOL ","MIX - Product Tank Drain Manual Request " +"M19111 ","M 1911.1 ","BOOL ","MIX - " +"M19112 ","M 1911.2 ","BOOL ","MIX - " +"M19113 ","M 1911.3 ","BOOL ","MIX - " +"M19114 ","M 1911.4 ","BOOL ","MIX - " +"M19115 ","M 1911.5 ","BOOL ","MIX - " +"M19116 ","M 1911.6 ","BOOL ","MIX - Interlocking Net : General Communication Error " +"M19117 ","M 1911.7 ","BOOL ","MIX - " +"M19120 ","M 1920.0 ","BOOL ","MIX - Valves Global Ssfety " +"M19150 ","M 1915.0 ","BOOL ","MIX - FP ****** " +"M19151 ","M 1915.1 ","BOOL ","MIX - FP ****** " +"M19152 ","M 1915.2 ","BOOL ","MIX - FP ****** " +"M19153 ","M 1915.3 ","BOOL ","MIX - FP ****** " +"M19154 ","M 1915.4 ","BOOL ","MIX - " +"M19155 ","M 1915.5 ","BOOL ","MIX - " +"M19156 ","M 1915.6 ","BOOL ","MIX - FP ****** " +"M19157 ","M 1915.7 ","BOOL ","MIX - FP ****** " +"M19505 ","M 1950.5 ","BOOL ","MIX - " +"M19506 ","M 1950.6 ","BOOL ","MIX - " +"M19507 ","M 1950.7 ","BOOL ","MIX - " +"M19510 ","M 1951.0 ","BOOL ","MIX - I/O Point Fault Memory " +"M19511 ","M 1951.1 ","BOOL ","MIX - Loss Of Rack Fault Memory " +"M19512 ","M 1951.2 ","BOOL ","MIX - Complete Restart Memory " +"M19513 ","M 1951.3 ","BOOL ","MIX - Profibus diag "CHECK_ACTIVE" " +"M19514 ","M 1951.4 ","BOOL ","MIX - Profibus diag "HMI RESET - CHECK_ACTIVE" " +"M19515 ","M 1951.5 ","BOOL ","MIX - Profibus diag "HMI SINGLE DIAG REQUEST" " +"M19517 ","M 1951.7 ","BOOL ","MIX - " +"M20000 ","M 2000.0 ","BOOL ","MIX - Stop_SR " +"M20001 ","M 2000.1 ","BOOL ","MIX - ColdRinseSR " +"M20002 ","M 2000.2 ","BOOL ","MIX - WarmRinseSR " +"M20003 ","M 2000.3 ","BOOL ","MIX - " +"M20004 ","M 2000.4 ","BOOL ","MIX - mColdRinseSelected " +"M20005 ","M 2000.5 ","BOOL ","MIX - mWarmRinseSelected " +"M3.2 ","M 3.2 ","BOOL ","Modifica_16052022 " +"M3042 ","M 304.2 ","BOOL ","Start Up ready to Filler Aux " +"M305.2 ","M 305.2 ","BOOL ","Start Up with Flooding: Pressure Enable " +"M305.6 ","M 305.6 ","BOOL ","Cip : Filler Dummy Bottles Inserition Request FP " +"M305.7 ","M 305.7 ","BOOL ","Cip : Filler Dummy Bottles Inserition Request " +"M3051 ","M 305.1 ","BOOL ","Start Up with Flooding: Drain Activated ONS " +"M3053 ","M 305.3 ","BOOL ","Start Up with Flooding: Flooding Activated " +"M3054 ","M 305.4 ","BOOL ","Product Tank Bottom Calc FP " +"M3055 ","M 305.5 ","BOOL ","Product Tank Full FP " +"M306.0 ","M 306.0 ","BOOL ","EndProductionDrain " +"M4000 ","M 400.0 ","BOOL ","Aux PPM303 Slew 1 " +"M4001 ","M 400.1 ","BOOL ","Aux PPM303 Slew 2 " +"M50.0_Test_Sim ","M 50.0 ","BOOL ","_Simulation Active_ " +"M80.0 ","M 80.0 ","BOOL ","Aux FN per Cip Complete " +"M80.1 ","M 80.1 ","BOOL ","Aux FP First Production Done " +"M80.2 ","M 80.2 ","BOOL ","Mem. First Production Done " +"M80.3 ","M 80.3 ","BOOL ","Aux Mem. Prima First Prod Done for Maselli " +"M80.4 ","M 80.4 ","BOOL ","Aux Next Step After Blending in first prod " +"mAlarmHornReset ","M 1715.2 ","BOOL ","MIX - SAFETIES " +"Maselli_ADAM_Read ","FB 2121 ","FB 2121 ","Read AI with error control " +"Maselli_PA_Control ","FC 1890 ","FC 1890 ","MIX - Maselli_PA_Control " +"Maselli_PA_Ctrl_Read ","FC 1891 ","FC 1891 ","MIX - Maselli_PA_Ctrl_Read " +"Maselli_PA_Ctrl_Transfer","FC 1893 ","FC 1893 ","MIX - Maselli_PA_Ctrl_Transfer " +"Maselli_PA_Ctrl_Write ","FC 1892 ","FC 1892 ","MIX - Maselli_PA_Ctrl_Write " +"Maselli_PA_Data ","DB 967 ","DB 967 ","MIX - Maselli_PA_Data " +"MaselliTCP ","FB 2120 ","FB 2120 ","MaselliTCP Read " +"MaselliTCP_DB_UR29 ","DB 2120 ","FB 2120 "," " +"MaselliTCP_DB_UR62 ","DB 2123 ","FB 2120 "," " +"mAuxiliaryOn ","M 130.0 ","BOOL "," " +"mAuxStepICSMssg ","MW 620 ","INT ","MIX - ICS Aux Memory Step " +"mAuxStopFromFillerTmr ","T 17 ","TIMER ","MIX - BlenderRun__Control " +"mAuxTP1 ","T 140 ","TIMER ","MIX - MFMAnalogValues_Totalize " +"mAVN30x_1 ","M 1690.0 ","BOOL "," " +"mAVN30x_1_Old ","M 1691.0 ","BOOL "," " +"mAVN30x_2 ","M 1690.1 ","BOOL "," " +"mAVN30x_2_Old ","M 1691.1 ","BOOL "," " +"mAVN30x_3 ","M 1690.2 ","BOOL "," " +"mAVN30x_3_Old ","M 1691.2 ","BOOL "," " +"MaxCarboCO2 Vol ","FC 1912 ","FC 1912 ","MIX - Max_Carbo_CO2_Vol " +"MB101 ","MB 101 ","BYTE ","QCO Database Pointer " +"mBaialage_Fault ","T 21 ","TIMER ","MIX - Baialage " +"mBaialage_Request ","M 1908.2 ","BOOL ","MIX - Baialage Request " +"mBaialageDelayMinflow ","T 20 ","TIMER ","MIX - Baialage " +"mBaialageReqONS ","M 1908.1 ","BOOL ","MIX - Baialage Request FP " +"mBalaiageDoneONS ","M 1908.5 ","BOOL ","MIX - FP ****** " +"mBlenderDrainTimer ","T 125 ","TIMER ","MIX - CIP Total Drain " +"mBlenderDrainTimerDelay ","T 182 ","TIMER ","MIX - CIP Total Drain Delay Timer " +"mBlenderEmprtyTON ","T 124 ","TIMER ","MIX - CIP MAIN " +"mBlenderStopDly ","T 10 ","TIMER ","MIX - BlenderRun__Control " +"mBlendStopDly ","T 139 ","TIMER ","MIX - MFMAnalogValues_Totalize " +"mCIP_CompletedONS ","M 1900.4 ","BOOL ","MIX - CIP MAIN " +"mCIPRinseDlyOpenRMP302 ","T 82 ","TIMER ","Time delay to open valve RMP302 in CIP/Rinse " +"mCIPRinseDlyOpenValve ","T 81 ","TIMER ","Time delay to open valve in CIP/Rinse " +"mClock_ONS ","M 1903.7 ","BOOL ","MIX - CIPLocal_ExecSimpleCIP " +"mCO2MainValveDelay ","T 44 ","TIMER ","MIX - ProductTank PressCtrl " +"mCO2TD ","MD 2016 ","REAL ","MIX - CIp Prod Flow " +"mCO2Td1Ons ","M 1725.5 ","BOOL "," " +"mCO2TdOns ","M 1725.4 ","BOOL "," " +"mCTS301HighAcheavedDly ","T 34 ","TIMER ","MIX - CIPLocal_WaitEvent_Ctrl " +"mCTS301LowAcheavedDly ","T 36 ","TIMER ","MIX - CIPLocal_WaitEvent_Ctrl " +"MD1200 ","MD 1200 ","DWORD ","Aux Liter Offset " +"MD1204 ","MD 1204 ","DWORD ","Aux deareator Start Up Liters Counter " +"MD1206 ","MD 1206 ","DWORD ","Aux CarboWater Line Offset " +"MD1870 ","MD 1870 ","DWORD ","MIX - Profibus network Fault array 1 " +"MD1874 ","MD 1874 ","DWORD ","MIX - Profibus network Fault array 2 " +"MD1878 ","MD 1878 ","DWORD ","MIX - Profibus network Fault array 3 " +"MD1882 ","MD 1882 ","DWORD ","MIX - Profibus network Fault array 4 " +"mDeairLSN301_LevFault ","T 77 ","TIMER ","MIX - TankLevel " +"mDeairLSN301_LevFault1 ","T 78 ","TIMER ","MIX - TankLevel " +"mDeairLSN301_LevFault2 ","T 79 ","TIMER ","MIX - TankLevel " +"mDeairTank_Flood ","T 129 ","TIMER ","MIX - CIP Flood " +"mDeairTank_HighLvlONS ","M 1909.0 ","BOOL ","MIX - High deair level fault FP " +"mDeairTank_LevFault ","T 58 ","TIMER ","MIX - TankLevel " +"mDeairTank_LevFault1 ","T 59 ","TIMER ","MIX - TankLevel " +"mDeairTank_LevFault2 ","T 60 ","TIMER ","MIX - TankLevel " +"mDeairTank_LevFault3 ","T 148 ","TIMER ","MIX - TankLevel " +"mDeairTank_LoadDelay ","T 57 ","TIMER ","MIX - TankLevel " +"mDeairTank2_Flood ","T 120 ","TIMER ","Timer to Mix the first prod " +"mDelayBlendEn ","T 11 ","TIMER ","MIX - BlenderRun__Control " +"mDelayCIPStopP1 ","T 46 ","TIMER ","MIX - PumpsControl " +"mDelayCIPStopP2 ","T 51 ","TIMER ","MIX - PumpsControl " +"mDelayCIPStopP3 ","T 53 ","TIMER ","MIX - PumpsControl " +"mDelayCIPStopP5 ","T 49 ","TIMER ","MIX - PumpsControl " +"mDelayCIPStopP9 ","T 76 ","TIMER ","MIX - PumpsControl " +"mDelayCIPStopPPM307 ","T 27 ","TIMER ","MIX - PumpsControl " +"mDelayCipStopPPN301 ","T 149 ","TIMER ","MIX - Pumps Control " +"mDelayOpenAVM317 ","T 29 ","TIMER ","MIX - Pneumatic Valve Ctrl " +"mDelayPowerOnTmr ","T 0 ","TIMER ","MIX - BlenderCtrl__Main " +"mDelayProdStopPPM303 ","T 52 ","TIMER ","MIX - PumpsControl " +"mDelayProdStopPPM309 ","T 73 ","TIMER ","MIX - PumpsControl " +"mDelayStartPPM307 ","T 26 ","TIMER ","MIX - PumpsControl " +"mDelayStartPPN301 ","T 92 ","TIMER ","MIX - PumpsControl " +"mDelayStartPPN305 ","T 48 ","TIMER ","MIX - PumpsControl " +"mDelayStartPPN305Blend ","T 47 ","TIMER ","MIX - PumpsControl " +"mDelayStartPPP302 ","T 50 ","TIMER ","MIX - PumpsControl " +"mDelayStartProdPPN301 ","T 89 ","TIMER ","MIX - PumpsControl " +"mDelayStop ","T 9 ","TIMER ","MIX - BlenderRun__Control " +"mDelayStop_SR_P ","T 54 ","TIMER ","MIX - PumpsControl " +"mDelayStopPPN301 ","T 150 ","TIMER ","MIX - PumpsControl " +"mDelayStopPPN305 ","T 152 ","TIMER ","MIX - PumpsControl " +"mDelayStopProdPPN301 ","T 157 ","TIMER ","MIX - PumpsControl " +"mDelayToRestart ","T 18 ","TIMER ","MIX - BlenderRun__Control " +"mDeoxCIPPressTmr ","T 99 ","TIMER ","MIX - Deox CIP Delta Inlet Pressure Timer " +"mDeoxLowPressTmr ","T 71 ","TIMER ","MIX - Deox Low Inlet Pressure Timer " +"mDeoxNoPressTmr ","T 72 ","TIMER ","MIX - Deox No Inlet Pressure Timer " +"mDeoxPressCtrlDly ","T 70 ","TIMER ","MIX - Deox Inlet Pressure Control Delay " +"mDlyCarboStop ","T 65 ","TIMER ","MIX - Delay Carbo Stop OFF " +"mDlyCIPSysLevelOK ","T 87 ","TIMER ","Time delay to CIP System Level OK " +"mDlyFreeze ","T 66 ","TIMER ","MIX - Delay Freeze Meter OFF " +"mDlyOffRecirc ","T 15 ","TIMER ","MIX - BlenderRun__Control " +"mDlyReturnFlowOff ","T 88 ","TIMER ","ICS - Delay to Return Flow OFF " +"mDlyReturnFlowOn ","T 98 ","TIMER ","ICS - Delay to Return flow ON " +"mEHS301_Fault ","T 114 ","TIMER ","MIX - EHS Control " +"mEHS302_Fault ","T 115 ","TIMER ","MIX - EHS Control " +"mEHS303_Fault ","T 116 ","TIMER ","MIX - EHS Control " +"mEHS304_Fault ","T 117 ","TIMER ","MIX - EHS Control " +"mEHS305_Fault ","T 118 ","TIMER ","MIX - EHS Control " +"mEHS306_Fault ","T 119 ","TIMER ","MIX - EHS Control " +"mEnableStartTmr ","T 8 ","TIMER ","MIX - BlenderRun__Control " +"mEnCooler ","T 14 ","TIMER ","MIX - BlenderRun__Control " +"mEnStopFromFillerTmr ","T 16 ","TIMER ","MIX - BlenderRun__Control " +"mEnToRamp ","T 13 ","TIMER ","MIX - BlenderRun__Control " +"mEqPressOk ","T 45 ","TIMER ","MIX - ProductTank PressCtrl " +"MessageScroll ","FC 210 ","FC 210 ","From Bit to Code conversion " +"MessageScroll_Time ","T 142 ","TIMER ","MIX - MessageScroll " +"mFaultCloseRMM301Tmr ","T 38 ","TIMER ","MIX - ModValveFault " +"mFaultCloseRMM303Tmr ","T 42 ","TIMER ","MIX - ModValveFault " +"mFaultCloseRMM304Tmr ","T 68 ","TIMER ","MIX - ModValveFault " +"mFaultCloseRMP302Tmr ","T 40 ","TIMER ","MIX - ModValveFault " +"mFaultOpenRMM301Tmr ","T 37 ","TIMER ","MIX - ModValveFault " +"mFaultOpenRMM303Tmr ","T 41 ","TIMER ","MIX - ModValveFault " +"mFaultOpenRMM304Tmr ","T 67 ","TIMER ","MIX - ModValveFault " +"mFaultOpenRMP302Tmr ","T 39 ","TIMER ","MIX - ModValveFault " +"mFillerFirstLoadTimeOut ","M 2000.6 ","BOOL ","MIX - BlendFill StartUp " +"mFillerProdLoadedInit ","MD 2004 ","REAL ","MIX - BlendFill StartUp " +"mFinalPressOk ","T 91 ","TIMER ","MIX - ProductTank PressCtrl " +"mFlipFlop_1 ","T 101 ","TIMER ","MIX - Pneumatic Valve Ctrl " +"mFlipFlop_2 ","T 102 ","TIMER ","MIX - Pneumatic Valve Ctrl " +"mFlipFlop_3 ","T 103 ","TIMER ","MIX - Pneumatic Valve Ctrl " +"mFlipFlop_4 ","T 104 ","TIMER ","MIX - Pneumatic Valve Ctrl " +"mFlipFlopTOF ","T 31 ","TIMER ","MIX - Pneumatic Valve Ctrl " +"mFlipFlopTOF1 ","T 107 ","TIMER ","MIX - ProductTankDrain " +"mFlipFlopTON ","T 30 ","TIMER ","MIX - Pneumatic Valve Ctrl " +"mFlipFlopTON1 ","T 106 ","TIMER ","MIX - ProductTankDrain " +"MFM_Analog_Value_Data ","DB 942 ","FB 1720 ","MIX - MFM_Analog_Value_Data " +"MFMAnalogValues ","FB 1720 ","FB 1720 ","MIX - MFM_Analog_Values " +"MFMAnalogValues_Totalize","FC 2025 ","FC 2025 ","MIX - Totalizers " +"mICSCheckDosingMoreOFF ","T 96 ","TIMER ","MIX - ICS Check Conductivity to Dosing More Delay OFF " +"mICSCheckDosingMoreON ","T 95 ","TIMER ","MIX - ICS Check Conductivity to Dosing More Delay ON " +"mICSCIPDrainDone ","T 97 ","TIMER ","MIX - ICS " +"mICSDelayFlushingOFF ","T 110 ","TIMER ","MIX - ICS Delay to Stop Flushing " +"mInizialize_ONS ","M 1904.0 ","BOOL ","MIX - CIPLocal_ExecSimpleCIP " +"mInizializeTP ","T 23 ","TIMER ","MIX - CIP Local " +"mmH2O_TO_Freq ","FC 1844 ","FC 1844 ","MIX - mmH2O_TO_Freq " +"mNoRecircFlowDly ","T 24 ","TIMER ","MIX - CIP Local " +"MOD_ERR ","OB 122 ","OB 122 ","Module Access Error " +"ModValveFault ","FC 2035 ","FC 2035 ","MIX - Mod Valve Fault " +"mONS ","M 1900.6 ","BOOL ","MIX - CIP Local " +"mONS2 ","M 1907.2 ","BOOL ","MIX - CIP Local " +"mOpenAVS333_335TOF ","T 35 ","TIMER ","MIX - CIPLocal_WaitEvent_Ctrl " +"mOperatorStopTP ","T 22 ","TIMER ","MIX - BlenderRun_Stopping " +"mPAMaselli_FreezeMeter ","T 183 ","TIMER "," " +"mPAMaselli_RecipeFTRIG ","M 1909.7 ","BOOL ","MIX - mPAMaselli_RecipeFTRIG " +"mPAMaselli_RecipeRTRIG ","M 1909.6 ","BOOL ","MIX - mPAMaselli_RecipeRTRIG " +"mPAMaselli_RecipeTmr ","T 170 ","TIMER ","MIX - Maselli_PA_Ctrl_Transfer " +"mPAMaselli_RecipeTmr1 ","T 171 ","TIMER ","MIX - Maselli_PA_Ctrl_Write " +"mPAMaselli_TrnsfrFault ","T 173 ","TIMER ","MIX - Maselli_PA_Ctrl_Transfer Fault " +"mPAMaselliTmrFault ","T 172 ","TIMER ","MIX - Maselli_PA_Ctrl_Read " +"mPAMaselliTmrFault1 ","T 175 ","TIMER ","MIX - Maselli_PA_Ctrl_Error " +"mPAMaselliTmrReset ","T 32 ","TIMER ","MIX - Maselli_PA_Control " +"mPAmPDS_SYR_FreezeMeter ","T 177 ","TIMER ","MIX - mPDS_SYR_PA_Control " +"mPAmPDS_SYR_TmrFault ","T 174 ","TIMER ","MIX - mPDS_SYR_PA_Ctrl_Read " +"mPAmPDS_SYR_TmrReset ","T 176 ","TIMER ","MIX - mPDS_SYR_PA_Control " +"mPAmPDSCARBOStopMeterTmr","T 161 ","TIMER ","MIX - mPDS_PA_Control " +"mPAmPDSFreezeMeterTmr ","T 160 ","TIMER ","MIX - mPDS_PA_Control " +"mPAmPDSTmrComErr ","T 162 ","TIMER ","MIX - mPDS_PA_Ctrl_Read " +"mPAmPDSTmrFault ","T 163 ","TIMER ","MIX - mPDS_PA_Ctrl_Read " +"mPAmPDSTmrReset ","T 159 ","TIMER ","MIX - mPDS_PA_Control " +"mPDS_PA_Control ","FC 1880 ","FC 1880 ","MIX - mPDS_PA_Control " +"mPDS_PA_Ctrl_Parameters ","FC 1883 ","FC 1883 ","MIX - Parameters_Transfer " +"mPDS_PA_Ctrl_Read ","FC 1881 ","FC 1881 ","MIX - mPDS_PA_Control_Read " +"mPDS_PA_Ctrl_Transfer ","FC 1884 ","FC 1884 ","MIX - Transfer_PA_mPDS " +"mPDS_PA_Ctrl_Write ","FC 1882 ","FC 1882 ","MIX - mPDS_PA_Control_Write " +"mPDS_PA_Data ","DB 932 ","DB 932 ","MIX - mPDS_PA_Data " +"mPDS_PA_OutDigitByteDI00","M 1792.0 ","BOOL ","MIX - Antoon Paar Virtual DI00 " +"mPDS_PA_OutDigitByteDI01","M 1792.1 ","BOOL ","MIX - Antoon Paar Virtual DI01 " +"mPDS_PA_OutDigitByteDI02","M 1792.2 ","BOOL ","MIX - Antoon Paar Virtual DI02 " +"mPDS_PA_OutDigitByteDI03","M 1792.3 ","BOOL ","MIX - Antoon Paar Virtual DI03 " +"mPDS_PA_OutDigitByteDI04","M 1792.4 ","BOOL ","MIX - Antoon Paar Virtual DI04 " +"mPDS_PA_OutDigitByteDI05","M 1792.5 ","BOOL ","MIX - Antoon Paar Virtual DI05 " +"mPDS_PA_OutDigitByteDI06","M 1792.6 ","BOOL ","MIX - Antoon Paar Virtual DI06 " +"mPDS_PA_OutDigitByteDI07","M 1792.7 ","BOOL ","MIX - Antoon Paar Virtual DI07 " +"mPDS_PA_OutDigitByteDI10","M 1793.0 ","BOOL ","MIX - Antoon Paar Virtual DI10 " +"mPDS_PA_OutDigitByteDI11","M 1793.1 ","BOOL ","MIX - Antoon Paar Virtual DI11 " +"mPDS_PA_OutDigitByteDI12","M 1793.2 ","BOOL ","MIX - Antoon Paar Virtual DI12 " +"mPDS_PA_OutDigitByteDI13","M 1793.3 ","BOOL ","MIX - Antoon Paar Virtual DI13 " +"mPDS_PA_OutDigitByteDI14","M 1793.4 ","BOOL ","MIX - Antoon Paar Virtual DI14 " +"mPDS_PA_OutDigitByteDI15","M 1793.5 ","BOOL ","MIX - Antoon Paar Virtual DI15 " +"mPDS_PA_OutDigitByteDI16","M 1793.6 ","BOOL ","MIX - Antoon Paar Virtual DI16 " +"mPDS_PA_OutDigitByteDI17","M 1793.7 ","BOOL ","MIX - Antoon Paar Virtual DI17 " +"mPDS_PA_OutDigitWord ","MW 1792 ","WORD ","MIX - mPDS_PA_Data Cyclical Output Data " +"mPDS_SYR_PA_Control ","FC 1885 ","FC 1885 ","MIX - mPDS_SYR_PA_Control " +"mPDS_SYR_PA_Ctrl_Param ","FC 1888 ","FC 1888 ","MIX - mPDS_SYR_PA_Parameters_Transfer " +"mPDS_SYR_PA_Ctrl_Read ","FC 1886 ","FC 1886 ","MIX - mPDS_SYR_PA_Control_Read " +"mPDS_SYR_PA_Ctrl_Trans ","FC 1889 ","FC 1889 ","MIX - mPDS_SYR_PA_Transfer " +"mPDS_SYR_PA_Ctrl_Write ","FC 1887 ","FC 1887 ","MIX - mPDS_SYR_PA_Control_Write " +"mPDS_SYR_PA_Data ","DB 966 ","DB 966 ","MIX - mPDS_SYR_PA_Data " +"mPDS5_P1 ","M 1794.0 ","BOOL ","MIX - mPDS5 " +"mPDS5_P2 ","M 1794.1 ","BOOL ","MIX - mPDS5 " +"mPID_AVN30x_Int ","MD 1922 ","REAL ","MIX - " +"mPID_RMM301_Int ","MD 1970 ","REAL ","MIX - PID_Water_Integral " +"mPID_RMM303_Int ","MD 1978 ","REAL ","MIX - PID_CO2_Integral " +"mPID_RMM304_Int ","MD 1926 ","REAL ","MIX - PID_GAS2_Integral " +"mPID_RMP302_Int ","MD 1974 ","REAL ","MIX - PID_Syrup_Integral " +"mPID_RVN302_Int ","MD 1986 ","REAL ","MIX - PID_RVN302_Level_Integral " +"mPID_RVP303_Int ","MD 1982 ","REAL ","MIX - PID_Syrup_Level_Integral " +"mPPM303_Pump ","T 155 ","TIMER ","MIX - PumpsControl " +"mPPM303RampEndFreq ","MD 1744 ","REAL ","PPM303 end ramp frequency " +"mPPM303RampFreq ","MD 1726 ","REAL ","PPM303 ramp freq aux " +"mPPM303RampSlewMax ","MD 1730 ","REAL ","PPM303 ramp max slew " +"mPPM303StartUpRamp ","DB 702 ","FB 1702 ","BlendFill StartUP Ramp " +"mPPM306_Pump ","T 158 ","TIMER ","MIX - PumpsControl " +"mPPM307_Pump ","T 28 ","TIMER ","MIX - PumpsControl " +"mPPM309_Pump ","T 83 ","TIMER ","MIX - PumpsControl " +"mPPN301_Pump ","T 151 ","TIMER ","MIX - PumpsControl " +"mPPN304_Pump ","T 156 ","TIMER ","MIX - PumpsControl " +"mPPN305_Pump ","T 153 ","TIMER ","MIX - PumpsControl " +"mPPP302_Pump ","T 154 ","TIMER ","MIX - PumpsControl " +"mPrdTank_Flood ","T 127 ","TIMER ","MIX - CIP Flood " +"mProdTankPressCO2_N2 ","M 1700.2 ","BOOL ","Prod Tank Press CO2+N2 " +"mProdTankPressCO2_StAir ","M 1700.1 ","BOOL ","Prod Tank Press CO2+StAir " +"mProdTankPressOnlyCO2 ","M 1700.0 ","BOOL ","Prod Tank Press Only CO2 " +"mRecipeCalculationONS ","M 1921.1 ","BOOL ","MIX - Save Syrup Level Integral ONS " +"mRecipeChanged_ONS ","M 1904.1 ","BOOL ","MIX - CIPLocal_ExecSimpleCIP " +"mRequestTP ","M 1900.5 ","BOOL ","MIX - CIP Local " +"mResetFTM303TotTmr ","T 6 ","TIMER ","MIX - BlenderCtrl__Main " +"mResetFTN301TotTmr ","T 4 ","TIMER ","MIX - BlenderCtrl__Main " +"mResetFTP302TotTmr ","T 5 ","TIMER ","MIX - BlenderCtrl__Main " +"mResetProductTotTmr ","T 7 ","TIMER ","MIX - BlenderCtrl__Main " +"mResetTotalizerTmr ","T 3 ","TIMER ","MIX - BlenderCtrl__Main " +"mRMM301_Int_Init ","M 1920.3 ","BOOL ","MIX - Save Syrup Level Integral FP " +"mRMM301_Int_ONS ","M 1920.4 ","BOOL ","MIX - Save Syrup Level Integral ONS " +"mRMM303_Int_Init ","M 1920.7 ","BOOL ","MIX - Save Syrup Level Integral FP " +"mRMM303_Int_ONS ","M 1921.0 ","BOOL ","MIX - Save Syrup Level Integral ONS " +"mRMM304_Int_Init ","M 1921.4 ","BOOL ","MIX - Save GAS 2 Flow Integral FP " +"mRMM304_Int_ONS ","M 1921.5 ","BOOL ","MIX - Save GAS 2 Flow Integral ONS " +"mRMP302_Int_Init ","M 1920.5 ","BOOL ","MIX - Save Syrup Level Integral FP " +"mRMP302_Int_ONS ","M 1920.6 ","BOOL ","MIX - Save Syrup Level Integral ONS " +"mRVM301_Fault ","T 43 ","TIMER ","MIX - ProductTank PressCtrl " +"mRVN302_Int_Init ","M 1921.2 ","BOOL ","MIX - Save Deair Level Integral FP " +"mRVN302_Int_ONS ","M 1921.3 ","BOOL ","MIX - Save Deair Level Integral ONS " +"mRVP303_Int_Init ","M 1920.1 ","BOOL ","MIX - Save Syrup Level Integral FP " +"mRVP303_Int_ONS ","M 1920.2 ","BOOL ","MIX - Save Syrup Level Integral ONS " +"mSaveNumTank ","MW 1994 ","INT ","MIX - mSaveNumTank " +"MSE Slope ","FB 1703 ","FB 1703 ","MIX - MSE Slope " +"mSpeedConstTmr ","T 138 ","TIMER ","MIX - BlenderPIDCtrl_SaveValve " +"mSpeedConstTmr.Q ","M 2010.7 ","BOOL ","MIX - BlenderPIDCtrl_SaveInteg " +"mStepDown_ONS ","M 1903.6 ","BOOL ","MIX - CIPLocal_ExecSimpleCIP " +"mSteppingDown ","M 1680.1 ","BOOL ","MIX - CIP Stepping Down " +"mSteppingUp ","M 1680.0 ","BOOL ","MIX - CIP Stepping Up " +"mStepUp_ONS ","M 1903.5 ","BOOL ","MIX - CIPLocal_ExecSimpleCIP " +"mSyrBxDelayAlrm ","T 56 ","TIMER ","MIX - SelCheckBrixSource " +"mSyrDensDelayAlrm ","T 55 ","TIMER ","MIX - SelCheckBrixSource " +"mSyrRoomH2OPumpFaultDly ","T 74 ","TIMER ","MIX - Input " +"mSyrRoomPumpFaultDly ","T 75 ","TIMER ","MIX - Input " +"mSyrTank_Flood ","T 128 ","TIMER ","MIX - CIP Flood " +"mSyrTank_HighLvlONS ","M 1908.7 ","BOOL ","MIX - High syrup level fault FP " +"mSyrTank_LevFault ","T 62 ","TIMER ","MIX - TankLevel " +"mSyrTank_LevFault1 ","T 63 ","TIMER ","MIX - TankLevel " +"mSyrTank_LevFault2 ","T 147 ","TIMER ","MIX - TankLevel " +"mSyrTank_LoadDelay ","T 61 ","TIMER ","MIX - TankLevel " +"mT1_VacuumCtrl_DelayON ","T 186 ","TIMER ","MIX - Vacuum Ctrl - Temperature Ctrl - Delay on Time 1 " +"mT1_VacuumCtrlTemp ","MW 610 ","S5TIME ","MIX - Vacuum Ctrl Temperature Time 1 " +"mT2_VacuumCtrl_DelayON ","T 187 ","TIMER ","MIX - Vacuum Ctrl - Temperature Ctrl - Delay on Time 2 " +"mT2_VacuumCtrlTemp ","MW 612 ","S5TIME ","MIX - Vacuum Ctrl Temperature Time 2 " +"mT3_VacuumCtrl_DelayOFF ","T 188 ","TIMER ","MIX - Vacuum Ctrl - Temperature Ctrl - Delay on Time 3 " +"mT3_VacuumCtrlTemp ","MW 614 ","S5TIME ","MIX - Vacuum Ctrl Temperature Time 3 " +"mT4_VacuumCtrl_DelayON ","T 189 ","TIMER ","MIX - Vacuum Ctrl - Temperature Ctrl - Delay on Time 4 " +"mT4_VacuumCtrlTemp ","MW 616 ","S5TIME ","MIX - Vacuum Ctrl Temperature Time 4 " +"MTD NumBottleAftEndP DB ","DB 2115 ","FB 2115 ","MTD Number of Bottle After End Prodruction DB " +"MTD NumBottleAftEndProd ","FB 2115 ","FB 2115 ","MTD Number of Bottle After End Prodruction " +"mTempAcheavedDly ","T 33 ","TIMER ","MIX - CIPLocal_WaitEvent_Ctrl " +"mTestingREAD_VLT_PPN301 ","M 300.1 ","BOOL "," " +"mTimeTN301_StartupFilter","T 123 ","TIMER ","Deaireator StartUp " +"mTmrCO2High ","T 84 ","TIMER ","MIX - ProductQuality " +"mTON_Reset_RecircPHE_Goo","T 80 ","TIMER ","MIX - BlenderRun__Control " +"mTTM306_H2O_HighDlyAlm ","T 85 ","TIMER ","MIX - ProductQuality " +"mTTM306_PRD_HighDlyAlm ","T 86 ","TIMER ","MIX - ProductQuality " +"mVacuumFaultTmr ","T 64 ","TIMER ","MIX - VacuumCtrl " +"mVacuumPumpStopDly ","T 25 ","TIMER ","MIX - Vacuum Pump Stop Delay after machine stop " +"MW1968 ","MW 1968 ","INT ","MIX - OB35 Scan cycle count " +"MW1996 ","MW 1996 ","INT ","MIX - Brix track - Sample Time Calc - Cycle Count " +"MW1998 ","MW 1998 ","INT ","MIX - CO2 Track -Sample Time Calc - Cycle Count " +"MW352 ","MW 352 ","INT ","Bottle Size " +"MW356 ","MW 356 ","INT ","Filling ValveHead_Sp " +"mWaitPPN305Run ","T 100 ","TIMER ","Wait blending for PPN305 running " +"mWaitStableFlow ","T 12 ","TIMER ","MIX - BlenderRun__Control " +"mWarningHornReset ","M 1715.3 ","BOOL ","MIX - SAFETIES " +"mWritePAmPDS_RT ","M 1909.2 ","BOOL ","MIX - mWritePAmPDS_RT FP " +"mWritePAmPDS_SYR_Counter","Z 2 ","COUNTER ","MIX - " +"mWritePAmPDS_SYR_RT ","M 1909.3 ","BOOL ","MIX - mWritePAmPDS_SYR_RT FP " +"NE_STRNG ","FC 29 ","FC 29 ","Not Equal String " +"Net BlendFill Eth ","DB 806 ","FB 1 ","MIX - Net FIller Simonazzi Blendfill Ethernet " +"Net Cip Sidel Eth ","DB 809 ","FB 1 ","MIX - Net CipRoom Sidel Ethernet " +"Net CIP System Eth ","DB 803 ","FB 1 ","MIX - Net CipRoom Ethernet " +"Net Dosing Sys Prof ","DB 810 ","DB 810 ","MIX - Net Dosing System Profibus " +"Net Filler Eth ","DB 805 ","FB 1 ","MIX - Net FIller Simonazzi no Blendfill Ethernet " +"Net Filler Sidel Eth ","DB 807 ","FB 1 ","MIX - Net FIller Sidel Ethernet " +"Net Pasto Eth ","DB 804 ","FB 1 ","MIX - Net PastoRoom Ethernet " +"Net ProdRoom Eth ","DB 808 ","FB 1 ","MIX - Net PrdRoom Ethernet " +"Net SyrupRoom Eth ","DB 802 ","FB 1 "," " +"OBNL_FLT ","OB 85 ","OB 85 ","OB Not Loaded Fault " +"ONS_R ","FC 1920 ","FC 1920 ","MIX - ONS_R " +"Output ","FC 2026 ","FC 2026 ","MIX - Output " +"Output_AnalogValueToHMI ","FC 2017 ","FC 2017 ","MIX - Analog Value To HMI " +"Output_CO2InjPress ","FC 2021 ","FC 2021 ","MIX - Output_CO2InjPress " +"PAmPDS_Aux_Real_IN ","MD 1752 ","REAL ","MIX - Aux Real Input " +"PAmPDS_SYR_Devis_Tmr ","T 178 ","TIMER ","MIX - mPDS_SYR_PA_Ctrl_Read " +"PID ","UDT 85 ","UDT 85 "," " +"PID MAIN Data ","DB 729 ","FB 1729 ","MIX - Data for PID Block Main " +"PID_AVN30x ","FB 1795 ","FB 1795 "," " +"PID_AVN30x_Data ","DB 995 ","FB 1795 "," " +"PID_Continuos ","FB 41 ","FB 41 ","CONTINUOS PID CONTROLLER " +"PID_EHS30X ","FB 1790 ","FB 1790 ","MIX - PID_CIP_TempElectric " +"PID_EHS30X_Data ","DB 987 ","FB 1790 ","MIX - PID_CIP_Temp_Data_Electric " +"PID_Filling_Head ","FB 1791 ","FB 1791 ","MIX - PID_Filling_Head " +"PID_Filling_Head-TE ","FB 17910 ","FB 17910 "," " +"PID_Filling_Head_Calc ","FB 1792 ","FB 1792 ","MIX - _Filling_Head_PID_Ctrl.Calcolous " +"PID_Filling_Head_Calc-TE","FB 17920 ","FB 17920 "," " +"PID_Filling_Head_Data ","DB 990 ","FB 1791 ","MIX - PID_Filling_Head_Data " +"PID_Filling_Head+SFC20 ","FB 17912 ","FB 17912 ","MIX - PID_Filling_Head-No BLKMOV ma move semplice " +"PID_Filling_HeadNoSFC20 ","FB 17911 ","FB 17911 ","MIX - PID_Filling_Head-No BLKMOV solo DB1013 " +"PID_RMM301 ","FB 1780 ","FB 1780 ","MIX - PID_Water " +"PID_RMM301_Data ","DB 980 ","FB 1780 ","MIX - PID_Water_Data " +"PID_RMM303 ","FB 1782 ","FB 1782 ","MIX - PID_CarboCO2 " +"PID_RMM303_Data ","DB 982 ","FB 1782 ","MIX - PID_CarboCO2_Data " +"PID_RMM304 ","FB 1794 ","FB 1794 ","MIX - PID_Gas2 " +"PID_RMM304_Data ","DB 994 ","FB 1794 ","MIX - PID_GAS2_Data " +"PID_RMP302 ","FB 1781 ","FB 1781 ","MIX - PID_Syrup " +"PID_RMP302_Data ","DB 981 ","FB 1781 ","MIX - PID_Syrup_Data " +"PID_RVM301 ","FB 1783 ","FB 1783 ","MIX - PID_Press_CO2 " +"PID_RVM301_Data ","DB 983 ","FB 1783 ","MIX - PID_Press_CO2_Data " +"PID_RVM302 ","FB 1779 ","FB 1779 "," " +"PID_RVM302_Data ","DB 779 ","FB 1779 "," " +"PID_RVM304 ","FB 1784 ","FB 1784 ","MIX - Prod_Tank_Press_Release " +"PID_RVM304_Data ","DB 984 ","FB 1784 ","MIX - PID_Prod_Tank_Press_Release " +"PID_RVM319 ","FB 1785 ","FB 1785 "," " +"PID_RVM319_Data ","DB 985 ","FB 1785 "," " +"PID_RVN302 ","FB 1793 ","FB 1793 ","MIX - PID_Water_Level " +"PID_RVN302_Data ","DB 992 ","FB 1793 ","MIX - PID_Water_Level_Data " +"PID_RVP303 ","FB 1789 ","FB 1789 ","MIX - PID_Syrup_Level " +"PID_RVP303_Data ","DB 989 ","FB 1789 ","MIX - PID_Syrup_Level_Data " +"PID_RVS318 ","FB 1786 ","FB 1786 ","MIX - PID_CIP_Temp " +"PID_RVS318_Data ","DB 986 ","FB 1786 ","MIX - PID_CIP_Temp_Data_Steam " +"PID_Variables ","DB 961 ","DB 961 ","MIX - PID Variables " +"PIDControl ","FB 40 ","FB 40 ","PID Management " +"Pneumatic Valve Ctrl ","FC 2020 ","FC 2020 ","MIX - Pneumatic Valve Ctrl " +"Pneumatic Valve Fault ","FC 1969 ","FC 1969 ","MIX - Pneumatic Valve Fault " +"Pneumatic Valve Fault DB","DB 969 ","DB 969 ","MIX - Pneumatic Valve Fault DB " +"PPM O2 ","FC 1910 ","FC 1910 ","MIX - PPM_O2 " +"PPM303_B00_ControlReady ","M 1941.0 ","BOOL ","MIX - FC_Status_Word " +"PPM303_B00_RefValue_LSB ","M 1943.0 ","BOOL ","MIX - FC_Control_Word " +"PPM303_B01_DriveReady ","M 1941.1 ","BOOL ","MIX - FC_Status_Word " +"PPM303_B01_RefValue_MSB ","M 1943.1 ","BOOL ","MIX - FC_Control_Word " +"PPM303_B02_Brake ","M 1943.2 ","BOOL ","MIX - FC_Control_Word " +"PPM303_B02_Coasting ","M 1941.2 ","BOOL ","MIX - FC_Status_Word " +"PPM303_B03_Coast ","M 1943.3 ","BOOL ","MIX - FC_Control_Word " +"PPM303_B03_Trip ","M 1941.3 ","BOOL ","MIX - FC_Status_Word " +"PPM303_B04_Error ","M 1941.4 ","BOOL ","MIX - FC_Status_Word " +"PPM303_B04_QuickStop ","M 1943.4 ","BOOL ","MIX - FC_Control_Word " +"PPM303_B05_HoldOutputFrq","M 1943.5 ","BOOL ","MIX - FC_Control_Word " +"PPM303_B05_Reserved ","M 1941.5 ","BOOL ","MIX - FC_Status_Word " +"PPM303_B06_RampStop ","M 1943.6 ","BOOL ","MIX - FC_Control_Word " +"PPM303_B06_Triplock ","M 1941.6 ","BOOL ","MIX - FC_Status_Word " +"PPM303_B07_Reset ","M 1943.7 ","BOOL ","MIX - FC_Control_Word " +"PPM303_B07_Warning ","M 1941.7 ","BOOL ","MIX - FC_Status_Word " +"PPM303_B08_Jog ","M 1942.0 ","BOOL ","MIX - FC_Control_Word " +"PPM303_B08_Speed_OK ","M 1940.0 ","BOOL ","MIX - FC_Status_Word " +"PPM303_B09_BusControl ","M 1940.1 ","BOOL ","MIX - FC_Status_Word " +"PPM303_B09_RampSelection","M 1942.1 ","BOOL ","MIX - FC_Control_Word " +"PPM303_B10_DataValid ","M 1942.2 ","BOOL ","MIX - FC_Control_Word " +"PPM303_B10_FreqLimit_OK ","M 1940.2 ","BOOL ","MIX - FC_Status_Word " +"PPM303_B11_InOperation ","M 1940.3 ","BOOL ","MIX - FC_Status_Word " +"PPM303_B11_Relay01Active","M 1942.3 ","BOOL ","MIX - FC_Control_Word " +"PPM303_B12_Drive_OK ","M 1940.4 ","BOOL ","MIX - FC_Status_Word " +"PPM303_B12_Relay04Active","M 1942.4 ","BOOL ","MIX - FC_Control_Word " +"PPM303_B13_SetUp_LSB ","M 1942.5 ","BOOL ","MIX - FC_Control_Word " +"PPM303_B13_Voltage_OK ","M 1940.5 ","BOOL ","MIX - FC_Status_Word " +"PPM303_B14_SetUp_MSB ","M 1942.6 ","BOOL ","MIX - FC_Control_Word " +"PPM303_B14_Torque_OK ","M 1940.6 ","BOOL ","MIX - FC_Status_Word " +"PPM303_B15_Reverse ","M 1942.7 ","BOOL ","MIX - FC_Control_Word " +"PPM303_B15_Timer_OK ","M 1940.7 ","BOOL ","MIX - FC_Status_Word " +"PPM303_FC_Control_Word ","MW 1942 ","WORD ","MIX - PPM303_ProductPump_CtrlWord_VFC_Ctrl " +"PPM303_FC_Status_Word ","MW 1940 ","WORD ","MIX - PPM303_ProductPump_StatusWord_VFC_Ctrl " +"PPM303_VFC_Ctrl ","FC 1871 ","FC 1871 ","MIX - PPM303_ProductPump_VFC_Ctrl " +"PPM306_B00_ControlReady ","M 1917.0 ","BOOL ","MIX - FC_Status_Word " +"PPM306_B00_RefValue_LSB ","M 1919.0 ","BOOL ","MIX - FC_Control_Word " +"PPM306_B01_DriveReady ","M 1917.1 ","BOOL ","MIX - FC_Status_Word " +"PPM306_B01_RefValue_MSB ","M 1919.1 ","BOOL ","MIX - FC_Control_Word " +"PPM306_B02_Brake ","M 1919.2 ","BOOL ","MIX - FC_Control_Word " +"PPM306_B02_Coasting ","M 1917.2 ","BOOL ","MIX - FC_Status_Word " +"PPM306_B03_Coast ","M 1919.3 ","BOOL ","MIX - FC_Control_Word " +"PPM306_B03_Trip ","M 1917.3 ","BOOL ","MIX - FC_Status_Word " +"PPM306_B04_Error ","M 1917.4 ","BOOL ","MIX - FC_Status_Word " +"PPM306_B04_QuickStop ","M 1919.4 ","BOOL ","MIX - FC_Control_Word " +"PPM306_B05_HoldOutputFrq","M 1919.5 ","BOOL ","MIX - FC_Control_Word " +"PPM306_B05_Reserved ","M 1917.5 ","BOOL ","MIX - FC_Status_Word " +"PPM306_B06_RampStop ","M 1919.6 ","BOOL ","MIX - FC_Control_Word " +"PPM306_B06_Triplock ","M 1917.6 ","BOOL ","MIX - FC_Status_Word " +"PPM306_B07_Reset ","M 1919.7 ","BOOL ","MIX - FC_Control_Word " +"PPM306_B07_Warning ","M 1917.7 ","BOOL ","MIX - FC_Status_Word " +"PPM306_B08_Jog ","M 1918.0 ","BOOL ","MIX - FC_Control_Word " +"PPM306_B08_Speed_OK ","M 1916.0 ","BOOL ","MIX - FC_Status_Word " +"PPM306_B09_BusControl ","M 1916.1 ","BOOL ","MIX - FC_Status_Word " +"PPM306_B09_RampSelection","M 1918.1 ","BOOL ","MIX - FC_Control_Word " +"PPM306_B10_DataValid ","M 1918.2 ","BOOL ","MIX - FC_Control_Word " +"PPM306_B10_FreqLimit_OK ","M 1916.2 ","BOOL ","MIX - FC_Status_Word " +"PPM306_B11_InOperation ","M 1916.3 ","BOOL ","MIX - FC_Status_Word " +"PPM306_B11_Relay01Active","M 1918.3 ","BOOL ","MIX - FC_Control_Word " +"PPM306_B12_Drive_OK ","M 1916.4 ","BOOL ","MIX - FC_Status_Word " +"PPM306_B12_Relay04Active","M 1918.4 ","BOOL ","MIX - FC_Control_Word " +"PPM306_B13_SetUp_LSB ","M 1918.5 ","BOOL ","MIX - FC_Control_Word " +"PPM306_B13_Voltage_OK ","M 1916.5 ","BOOL ","MIX - FC_Status_Word " +"PPM306_B14_SetUp_MSB ","M 1918.6 ","BOOL ","MIX - FC_Control_Word " +"PPM306_B14_Torque_OK ","M 1916.6 ","BOOL ","MIX - FC_Status_Word " +"PPM306_B15_Reverse ","M 1918.7 ","BOOL ","MIX - FC_Control_Word " +"PPM306_B15_Timer_OK ","M 1916.7 ","BOOL ","MIX - FC_Status_Word " +"PPM306_FC_Control_Word ","MW 1918 ","WORD ","MIX - PPM306_RecirculationPump_CtrlWord_VFC_Ctrl " +"PPM306_FC_Status_Word ","MW 1916 ","WORD ","MIX - PPM306_RecirculationPump_StatusWord_VFC_Ctrl " +"PPM306_VFC_Ctrl ","FC 1874 ","FC 1874 ","MIX - PPM306_RecirculationPump_VFC_Ctrl " +"PPM307_B00_ControlReady ","M 1855.0 ","BOOL ","MIX - FC_Status_Word " +"PPM307_B00_RefValue_LSB ","M 1857.0 ","BOOL ","MIX - FC_Control_Word " +"PPM307_B01_DriveReady ","M 1855.1 ","BOOL ","MIX - FC_Status_Word " +"PPM307_B01_RefValue_MSB ","M 1857.1 ","BOOL ","MIX - FC_Control_Word " +"PPM307_B02_Brake ","M 1857.2 ","BOOL ","MIX - FC_Control_Word " +"PPM307_B02_Coasting ","M 1855.2 ","BOOL ","MIX - FC_Status_Word " +"PPM307_B03_Coast ","M 1857.3 ","BOOL ","MIX - FC_Control_Word " +"PPM307_B03_Trip ","M 1855.3 ","BOOL ","MIX - FC_Status_Word " +"PPM307_B04_Error ","M 1855.4 ","BOOL ","MIX - FC_Status_Word " +"PPM307_B04_QuickStop ","M 1857.4 ","BOOL ","MIX - FC_Control_Word " +"PPM307_B05_HoldOutputFrq","M 1857.5 ","BOOL ","MIX - FC_Control_Word " +"PPM307_B05_Reserved ","M 1855.5 ","BOOL ","MIX - FC_Status_Word " +"PPM307_B06_RampStop ","M 1857.6 ","BOOL ","MIX - FC_Control_Word " +"PPM307_B06_Triplock ","M 1855.6 ","BOOL ","MIX - FC_Status_Word " +"PPM307_B07_Reset ","M 1857.7 ","BOOL ","MIX - FC_Control_Word " +"PPM307_B07_Warning ","M 1855.7 ","BOOL ","MIX - FC_Status_Word " +"PPM307_B08_Jog ","M 1856.0 ","BOOL ","MIX - FC_Control_Word " +"PPM307_B08_Speed_OK ","M 1854.0 ","BOOL ","MIX - FC_Status_Word " +"PPM307_B09_BusControl ","M 1854.1 ","BOOL ","MIX - FC_Status_Word " +"PPM307_B09_RampSelection","M 1856.1 ","BOOL ","MIX - FC_Control_Word " +"PPM307_B10_DataValid ","M 1856.2 ","BOOL ","MIX - FC_Control_Word " +"PPM307_B10_FreqLimit_OK ","M 1854.2 ","BOOL ","MIX - FC_Status_Word " +"PPM307_B11_InOperation ","M 1854.3 ","BOOL ","MIX - FC_Status_Word " +"PPM307_B11_Relay01Active","M 1856.3 ","BOOL ","MIX - FC_Control_Word " +"PPM307_B12_Drive_OK ","M 1854.4 ","BOOL ","MIX - FC_Status_Word " +"PPM307_B12_Relay04Active","M 1856.4 ","BOOL ","MIX - FC_Control_Word " +"PPM307_B13_SetUp_LSB ","M 1856.5 ","BOOL ","MIX - FC_Control_Word " +"PPM307_B13_Voltage_OK ","M 1854.5 ","BOOL ","MIX - FC_Status_Word " +"PPM307_B14_SetUp_MSB ","M 1856.6 ","BOOL ","MIX - FC_Control_Word " +"PPM307_B14_Torque_OK ","M 1854.6 ","BOOL ","MIX - FC_Status_Word " +"PPM307_B15_Reverse ","M 1856.7 ","BOOL ","MIX - FC_Control_Word " +"PPM307_B15_Timer_OK ","M 1854.7 ","BOOL ","MIX - FC_Status_Word " +"PPM307_FC_Control_Word ","MW 1856 ","WORD ","MIX - PPM307_BoosterPump_CtrlWord_VFC_Ctrl " +"PPM307_FC_Status_Word ","MW 1854 ","WORD ","MIX - PPM307_BoosterPump_StatusWord_VFC_Ctrl " +"PPM307_VFC_Ctrl ","FC 1876 ","FC 1876 ","MIX - PPM307_BoosterPump_VFC_Ctrl " +"PPN301_B00_ControlReady ","M 1933.0 ","BOOL ","MIX - FC_Status_Word " +"PPN301_B00_RefValue_LSB ","M 1935.0 ","BOOL ","MIX - FC_Control_Word " +"PPN301_B01_DriveReady ","M 1933.1 ","BOOL ","MIX - FC_Status_Word " +"PPN301_B01_RefValue_MSB ","M 1935.1 ","BOOL ","MIX - FC_Control_Word " +"PPN301_B02_Brake ","M 1935.2 ","BOOL ","MIX - FC_Control_Word " +"PPN301_B02_Coasting ","M 1933.2 ","BOOL ","MIX - FC_Status_Word " +"PPN301_B03_Coast ","M 1935.3 ","BOOL ","MIX - FC_Control_Word " +"PPN301_B03_Trip ","M 1933.3 ","BOOL ","MIX - FC_Status_Word " +"PPN301_B04_Error ","M 1933.4 ","BOOL ","MIX - FC_Status_Word " +"PPN301_B04_QuickStop ","M 1935.4 ","BOOL ","MIX - FC_Control_Word " +"PPN301_B05_HoldOutputFrq","M 1935.5 ","BOOL ","MIX - FC_Control_Word " +"PPN301_B05_Reserved ","M 1933.5 ","BOOL ","MIX - FC_Status_Word " +"PPN301_B06_RampStop ","M 1935.6 ","BOOL ","MIX - FC_Control_Word " +"PPN301_B06_Triplock ","M 1933.6 ","BOOL ","MIX - FC_Status_Word " +"PPN301_B07_Reset ","M 1935.7 ","BOOL ","MIX - FC_Control_Word " +"PPN301_B07_Warning ","M 1933.7 ","BOOL ","MIX - FC_Status_Word " +"PPN301_B08_Jog ","M 1934.0 ","BOOL ","MIX - FC_Control_Word " +"PPN301_B08_Speed_OK ","M 1932.0 ","BOOL ","MIX - FC_Status_Word " +"PPN301_B09_BusControl ","M 1932.1 ","BOOL ","MIX - FC_Status_Word " +"PPN301_B09_RampSelection","M 1934.1 ","BOOL ","MIX - FC_Control_Word " +"PPN301_B10_DataValid ","M 1934.2 ","BOOL ","MIX - FC_Control_Word " +"PPN301_B10_FreqLimit_OK ","M 1932.2 ","BOOL ","MIX - FC_Status_Word " +"PPN301_B11_InOperation ","M 1932.3 ","BOOL ","MIX - FC_Status_Word " +"PPN301_B11_Relay01Active","M 1934.3 ","BOOL ","MIX - FC_Control_Word " +"PPN301_B12_Drive_OK ","M 1932.4 ","BOOL ","MIX - FC_Status_Word " +"PPN301_B12_Relay04Active","M 1934.4 ","BOOL ","MIX - FC_Control_Word " +"PPN301_B13_SetUp_LSB ","M 1934.5 ","BOOL ","MIX - FC_Control_Word " +"PPN301_B13_Voltage_OK ","M 1932.5 ","BOOL ","MIX - FC_Status_Word " +"PPN301_B14_SetUp_MSB ","M 1934.6 ","BOOL ","MIX - FC_Control_Word " +"PPN301_B14_Torque_OK ","M 1932.6 ","BOOL ","MIX - FC_Status_Word " +"PPN301_B15_Reverse ","M 1934.7 ","BOOL ","MIX - FC_Control_Word " +"PPN301_B15_Timer_OK ","M 1932.7 ","BOOL ","MIX - FC_Status_Word " +"PPN301_FC_Control_Word ","MW 1934 ","WORD ","MIX - PPN301_WaterPump_CtrlWord_VFC_Ctrl " +"PPN301_FC_Status_Word ","MW 1932 ","WORD ","MIX - PPN301_WaterPump_StatusWord_VFC_Ctrl " +"PPN301_VFC_Ctrl ","FC 1872 ","FC 1872 ","MIX - PPN301_WaterPump_VFC_Ctrl " +"PPN304_B00_ControlReady ","M 2013.0 ","BOOL ","MIX - FC_Status_Word " +"PPN304_B00_RefValue_LSB ","M 2015.0 ","BOOL ","MIX - FC_Control_Word " +"PPN304_B01_DriveReady ","M 2013.1 ","BOOL ","MIX - FC_Status_Word " +"PPN304_B01_RefValue_MSB ","M 2015.1 ","BOOL ","MIX - FC_Control_Word " +"PPN304_B02_Brake ","M 2015.2 ","BOOL ","MIX - FC_Control_Word " +"PPN304_B02_Coasting ","M 2013.2 ","BOOL ","MIX - FC_Status_Word " +"PPN304_B03_Coast ","M 2015.3 ","BOOL ","MIX - FC_Control_Word " +"PPN304_B03_Trip ","M 2013.3 ","BOOL ","MIX - FC_Status_Word " +"PPN304_B04_Error ","M 2013.4 ","BOOL ","MIX - FC_Status_Word " +"PPN304_B04_QuickStop ","M 2015.4 ","BOOL ","MIX - FC_Control_Word " +"PPN304_B05_HoldOutputFrq","M 2015.5 ","BOOL ","MIX - FC_Control_Word " +"PPN304_B05_Reserved ","M 2013.5 ","BOOL ","MIX - FC_Status_Word " +"PPN304_B06_RampStop ","M 2015.6 ","BOOL ","MIX - FC_Control_Word " +"PPN304_B06_Triplock ","M 2013.6 ","BOOL ","MIX - FC_Status_Word " +"PPN304_B07_Reset ","M 2015.7 ","BOOL ","MIX - FC_Control_Word " +"PPN304_B07_Warning ","M 2013.7 ","BOOL ","MIX - FC_Status_Word " +"PPN304_B08_Jog ","M 2014.0 ","BOOL ","MIX - FC_Control_Word " +"PPN304_B08_Speed_OK ","M 2012.0 ","BOOL ","MIX - FC_Status_Word " +"PPN304_B09_BusControl ","M 2012.1 ","BOOL ","MIX - FC_Status_Word " +"PPN304_B09_RampSelection","M 2014.1 ","BOOL ","MIX - FC_Control_Word " +"PPN304_B10_DataValid ","M 2014.2 ","BOOL ","MIX - FC_Control_Word " +"PPN304_B10_FreqLimit_OK ","M 2012.2 ","BOOL ","MIX - FC_Status_Word " +"PPN304_B11_InOperation ","M 2012.3 ","BOOL ","MIX - FC_Status_Word " +"PPN304_B11_Relay01Active","M 2014.3 ","BOOL ","MIX - FC_Control_Word " +"PPN304_B12_Drive_OK ","M 2012.4 ","BOOL ","MIX - FC_Status_Word " +"PPN304_B12_Relay04Active","M 2014.4 ","BOOL ","MIX - FC_Control_Word " +"PPN304_B13_SetUp_LSB ","M 2014.5 ","BOOL ","MIX - FC_Control_Word " +"PPN304_B13_Voltage_OK ","M 2012.5 ","BOOL ","MIX - FC_Status_Word " +"PPN304_B14_SetUp_MSB ","M 2014.6 ","BOOL ","MIX - FC_Control_Word " +"PPN304_B14_Torque_OK ","M 2012.6 ","BOOL ","MIX - FC_Status_Word " +"PPN304_B15_Reverse ","M 2014.7 ","BOOL ","MIX - FC_Control_Word " +"PPN304_B15_Timer_OK ","M 2012.7 ","BOOL ","MIX - FC_Status_Word " +"PPN304_FC_Control_Word ","MW 2014 ","WORD ","MIX - PPN304_VacuumPump_CtrlWord_VFC_Ctrl " +"PPN304_FC_Status_Word ","MW 2012 ","WORD ","MIX - PPN304_VacuumPump_StatusWord_VFC_Ctrl " +"PPN304_VFC_Ctrl ","FC 1875 ","FC 1875 ","MIX - PPN304_VacuumPump_VFC_Ctrl " +"PPP302_B00_ControlReady ","M 1937.0 ","BOOL ","MIX - FC_Status_Word " +"PPP302_B00_RefValue_LSB ","M 1939.0 ","BOOL ","MIX - FC_Control_Word " +"PPP302_B01_DriveReady ","M 1937.1 ","BOOL ","MIX - FC_Status_Word " +"PPP302_B01_RefValue_MSB ","M 1939.1 ","BOOL ","MIX - FC_Control_Word " +"PPP302_B02_Brake ","M 1939.2 ","BOOL ","MIX - FC_Control_Word " +"PPP302_B02_Coasting ","M 1937.2 ","BOOL ","MIX - FC_Status_Word " +"PPP302_B03_Coast ","M 1939.3 ","BOOL ","MIX - FC_Control_Word " +"PPP302_B03_Trip ","M 1937.3 ","BOOL ","MIX - FC_Status_Word " +"PPP302_B04_Error ","M 1937.4 ","BOOL ","MIX - FC_Status_Word " +"PPP302_B04_QuickStop ","M 1939.4 ","BOOL ","MIX - FC_Control_Word " +"PPP302_B05_HoldOutputFrq","M 1939.5 ","BOOL ","MIX - FC_Control_Word " +"PPP302_B05_Reserved ","M 1937.5 ","BOOL ","MIX - FC_Status_Word " +"PPP302_B06_RampStop ","M 1939.6 ","BOOL ","MIX - FC_Control_Word " +"PPP302_B06_Triplock ","M 1937.6 ","BOOL ","MIX - FC_Status_Word " +"PPP302_B07_Reset ","M 1939.7 ","BOOL ","MIX - FC_Control_Word " +"PPP302_B07_Warning ","M 1937.7 ","BOOL ","MIX - FC_Status_Word " +"PPP302_B08_Jog ","M 1938.0 ","BOOL ","MIX - FC_Control_Word " +"PPP302_B08_Speed_OK ","M 1936.0 ","BOOL ","MIX - FC_Status_Word " +"PPP302_B09_BusControl ","M 1936.1 ","BOOL ","MIX - FC_Status_Word " +"PPP302_B09_RampSelection","M 1938.1 ","BOOL ","MIX - FC_Control_Word " +"PPP302_B10_DataValid ","M 1938.2 ","BOOL ","MIX - FC_Control_Word " +"PPP302_B10_FreqLimit_OK ","M 1936.2 ","BOOL ","MIX - FC_Status_Word " +"PPP302_B11_InOperation ","M 1936.3 ","BOOL ","MIX - FC_Status_Word " +"PPP302_B11_Relay01Active","M 1938.3 ","BOOL ","MIX - FC_Control_Word " +"PPP302_B12_Drive_OK ","M 1936.4 ","BOOL ","MIX - FC_Status_Word " +"PPP302_B12_Relay04Active","M 1938.4 ","BOOL ","MIX - FC_Control_Word " +"PPP302_B13_SetUp_LSB ","M 1938.5 ","BOOL ","MIX - FC_Control_Word " +"PPP302_B13_Voltage_OK ","M 1936.5 ","BOOL ","MIX - FC_Status_Word " +"PPP302_B14_SetUp_MSB ","M 1938.6 ","BOOL ","MIX - FC_Control_Word " +"PPP302_B14_Torque_OK ","M 1936.6 ","BOOL ","MIX - FC_Status_Word " +"PPP302_B15_Reverse ","M 1938.7 ","BOOL ","MIX - FC_Control_Word " +"PPP302_B15_Timer_OK ","M 1936.7 ","BOOL ","MIX - FC_Status_Word " +"PPP302_FC_Control_Word ","MW 1938 ","WORD ","MIX - PPP302_SyrupPump_CtrlWord_VFC_Ctrl " +"PPP302_FC_Status_Word ","MW 1936 ","WORD ","MIX - PPP302_SyrupPump_StatusWord_VFC_Ctrl " +"PPP302_VFC_Ctrl ","FC 1873 ","FC 1873 ","MIX - PPP302_SyrupPump_VFC_Ctrl " +"PRDRepComment ","MW 606 ","INT ","PRODUCTION Report Comment " +"PRDRepExtFaultPresent ","M 604.7 ","BOOL ","PRODUCTION Report External Fault Present " +"PRDRepExtFaultPresentAux","M 604.6 ","BOOL ","PRODUCTION Report External Fault Present Aux " +"PRDRepFaultPresent ","M 604.4 ","BOOL ","PRODUCTION Report Fault Present " +"PRDRepFaultPresentAux ","M 604.5 ","BOOL ","PRODUCTION Report Fault Present Aux " +"PRDRepStarted ","M 604.0 ","BOOL ","PRODUCTION Report Started " +"Press_Request ","M 1904.4 ","BOOL ","MIX - FP ****** " +"PROC Pump Hz_to_Pressure","FC 1989 ","FC 1989 "," " +"PROC Pump Parameters ","FC 1990 ","FC 1990 "," " +"PROC Pump Pressure_to_Hz","FC 1988 ","FC 1988 "," " +"PROC Water Density ","FC 1996 ","FC 1996 "," " +"Procedure_rins_syrup ","M 1000.1 ","BOOL ","Procedure_rins_syrup_open_valv " +"Procedure_rins_syrupP ","M 1000.0 ","BOOL ","Procedure_rins_syrup_pulse " +"Procedure_Variables ","DB 960 ","DB 960 ","MIX - Procedure Variables " +"Prod Tank Drain ","FB 1807 ","FB 1807 ","MIX - ProdTank Drain " +"Prod Tank Drain_Seq ","FC 1807 ","FC 1807 ","MIX - Prod Tank Drain Sequencer " +"Prod Tank PressCtrl ","FC 2016 ","FC 2016 ","MIX - Product Tank PressCtrl " +"Prod Tank RunOut ","FB 1808 ","FB 1808 ","MIX - ProdTank RunOut " +"Prod Tank RunOut_Seq ","FC 1808 ","FC 1808 ","MIX - Prod Tank RunOut Sequencer " +"ProdBrixRecovery ","FB 1806 ","FB 1806 ","MIX - ProdBrixRecovery " +"ProdBrixRecovery_BrixCal","FC 1806 ","FC 1806 ","MIX - ProdBrixRecovery Brix Calc " +"ProdBrixRecoveryReq ","M 1907.0 ","BOOL ","MIX - FP ****** " +"ProdPipeDrainReset ","M 1905.3 ","BOOL ","MIX - FP ****** " +"ProdPipeRunOutReset ","M 1904.5 ","BOOL ","MIX - FP ****** " +"ProdReportDB ","DB 601 ","DB 601 "," " +"ProdReportManager ","FC 601 ","FC 601 "," " +"ProdTankDrainReq ","M 1905.4 ","BOOL ","MIX - FP ****** " +"ProdTankDrainReset ","M 1905.5 ","BOOL ","MIX - FP ****** " +"ProdTankRunOutReq ","M 1904.7 ","BOOL ","MIX - FP ****** " +"ProdTankRunOutReq_ONS ","M 1904.2 ","BOOL ","MIX - FP ****** " +"ProdTankRunOutReset ","M 1905.0 ","BOOL ","MIX - FP ****** " +"ProductAvailable ","FB 1823 ","FB 1823 ","MIX - ProductAvailable " +"ProductLiterInTank ","FC 1951 ","FC 1951 ","MIX - Product Liter In Tank " +"ProductPipeDrain ","FB 1824 ","FB 1824 ","MIX - ProductPipeDrain " +"ProductPipeDrain_Seq ","FC 1824 ","FC 1824 ","MIX - ProductPipeDrain_Seq " +"ProductPipeRunOut ","FB 1825 ","FB 1825 ","MIX - ProductPipeRunOut " +"ProductPipeRunOut_Seq ","FC 1825 ","FC 1825 ","MIX - ProductPipeRunOut_Seq " +"ProductQuality ","FC 2008 ","FC 2008 ","MIX - Product Quality " +"ProductQuality_Messages ","FC 2030 ","FC 2030 ","MIX - Messages " +"Profibus Network ","FC 11 ","FC 11 ","Profibus node diagnostic " +"Profibus_Variables ","DB 973 ","DB 973 ","MIX - Profibus datas from instruments " +"PROG_ERR ","OB 121 ","OB 121 ","Programming Error " +"PulsePressure ","FC 1838 ","FC 1838 ","MIX - PulsePressure " +"PumpsControl ","FC 1870 ","FC 1870 ","MIX - PumpsControl " +"QCO Monitor ","FC 100 ","FC 100 "," " +"QCO Phase ","UDT 101 ","UDT 101 "," " +"QCO Timer ","UDT 100 ","UDT 100 "," " +"QCO Timing ","VAT 11 "," "," " +"QCO Timing DB ","DB 100 ","DB 100 "," " +"RACK_FLT ","OB 86 ","OB 86 ","Loss of Rack Fault " +"RD_REC ","SFC 59 ","SFC 59 ","MIX - Read a Data Record " +"RD_SINFO ","SFC 6 ","SFC 6 ","Read OB Start Information " +"RDSYSST ","SFC 51 ","SFC 51 ","MIX - Read a System Status List or Partial List " +"READ_DBL ","SFC 83 ","SFC 83 ","Read Data Block out of Load Memory " +"ReadAnalogIn ","FC 1971 ","FC 1971 ","MIX - Read Analog Data " +"ReadAnalogIn_Fault_DB ","DB 871 ","DB 871 ","MIX - ReadAnalogIn DB " +"Recipe #01 ","DB 401 ","DB 401 ","Recipe 1 " +"Recipe #02 ","DB 402 ","DB 402 ","Recipe 2 " +"Recipe #03 ","DB 403 ","DB 403 ","Recipe 3 " +"Recipe #04 ","DB 404 ","DB 404 ","Recipe 4 " +"Recipe #05 ","DB 405 ","DB 405 ","Recipe 5 " +"Recipe #06 ","DB 406 ","DB 406 ","Recipe 6 " +"Recipe #07 ","DB 407 ","DB 407 ","Recipe 7 " +"Recipe #08 ","DB 408 ","DB 408 ","Recipe 8 " +"Recipe #09 ","DB 409 ","DB 409 ","Recipe 9 " +"Recipe #10 ","DB 410 ","DB 410 ","Recipe 10 " +"Recipe #11 ","DB 411 ","DB 411 ","Recipe 11 " +"Recipe #12 ","DB 412 ","DB 412 ","Recipe 12 " +"Recipe #13 ","DB 413 ","DB 413 ","Recipe 13 " +"Recipe #14 ","DB 414 ","DB 414 ","Recipe 14 " +"Recipe #15 ","DB 415 ","DB 415 ","Recipe 15 " +"Recipe #16 ","DB 416 ","DB 416 ","Recipe 16 " +"Recipe #17 ","DB 417 ","DB 417 ","Recipe 17 " +"Recipe #18 ","DB 418 ","DB 418 ","Recipe 18 " +"Recipe #19 ","DB 419 ","DB 419 ","Recipe 19 " +"Recipe #20 ","DB 420 ","DB 420 ","Recipe 20 " +"Recipe #21 ","DB 421 ","DB 421 ","Recipe 21 " +"Recipe #22 ","DB 422 ","DB 422 ","Recipe 22 " +"Recipe #23 ","DB 423 ","DB 423 ","Recipe 23 " +"Recipe #24 ","DB 424 ","DB 424 ","Recipe 24 " +"Recipe #25 ","DB 425 ","DB 425 ","Recipe 25 " +"Recipe #26 ","DB 426 ","DB 426 ","Recipe 26 " +"Recipe #27 ","DB 427 ","DB 427 ","Recipe 27 " +"Recipe #28 ","DB 428 ","DB 428 ","Recipe 28 " +"Recipe #29 ","DB 429 ","DB 429 ","Recipe 29 " +"Recipe #30 ","DB 430 ","DB 430 ","Recipe 30 " +"Recipe #31 ","DB 431 ","DB 431 ","Recipe 31 " +"Recipe #32 ","DB 432 ","DB 432 ","Recipe 32 " +"Recipe #33 ","DB 433 ","DB 433 ","Recipe 33 " +"Recipe #34 ","DB 434 ","DB 434 ","Recipe 34 " +"Recipe #35 ","DB 435 ","DB 435 ","Recipe 35 " +"Recipe #36 ","DB 436 ","DB 436 ","Recipe 36 " +"Recipe #37 ","DB 437 ","DB 437 ","Recipe 37 " +"Recipe #38 ","DB 438 ","DB 438 ","Recipe 38 " +"Recipe #39 ","DB 439 ","DB 439 ","Recipe 39 " +"Recipe #40 ","DB 440 ","DB 440 ","Recipe 40 " +"Recipe #41 ","DB 441 ","DB 441 ","Recipe 41 " +"Recipe #42 ","DB 442 ","DB 442 ","Recipe 42 " +"Recipe #43 ","DB 443 ","DB 443 ","Recipe 43 " +"Recipe #44 ","DB 444 ","DB 444 ","Recipe 44 " +"Recipe #45 ","DB 445 ","DB 445 ","Recipe 45 " +"Recipe #46 ","DB 446 ","DB 446 ","Recipe 46 " +"Recipe #47 ","DB 447 ","DB 447 ","Recipe 47 " +"Recipe #48 ","DB 448 ","DB 448 ","Recipe 48 " +"Recipe #49 ","DB 449 ","DB 449 ","Recipe 49 " +"Recipe #50 ","DB 450 ","DB 450 ","Recipe 50 " +"Recipe_Prod ","UDT 904 ","UDT 904 ","MIX - Recipe_Prod " +"RecipeCalculation ","FC 1798 ","FC 1798 ","MIX - Recipe Calculation " +"RecipeEditDataSave ","DB 400 ","DB 400 ","Recipe Data Edit -Save data " +"RecipeManagement - CIP ","FB 1799 ","FB 1799 ","CIP Link Recipe Edit, Save, Delete, Copy ... " +"RecipeManagement - Prod ","FB 1798 ","FB 1798 ","MIX - Main Machine Recipe Edit, Save, Delete, Copy ... " +"RecipeManagement_DataCIP","DB 399 ","FB 1799 ","MIX - CIP Recipe Management Data " +"RecipeManagement_DataPrd","DB 398 ","FB 1798 ","MIX - Recipe Management Data " +"REPLACE ","FC 31 ","FC 31 ","Replace Part of String " +"ReportCIPSimpleData ","UDT 601 ","UDT 601 "," " +"Reset Init ONS Prod Pump","M 1904.3 ","BOOL ","MIX - FP Reset Init ONS Prod Pump " +"Safeties ","FC 2022 ","FC 2022 ","MIX - Safeties " +"SE Timer ","FC 39 ","FC 39 ","SE Timer " +"Sel_Check_Brix_Data ","DB 931 ","FB 1703 ","MIX - MSE Slope of Sel Check Brix Source Data " +"SEL_I ","FC 1847 ","FC 1847 ","MIX - Selection Switch between 2 integers " +"SEL_R ","FC 1846 ","FC 1846 ","MIX - Selection Switch between 2 reals " +"SelCheckBrixSource ","FC 2010 ","FC 2010 ","MIX - Sel Check Brix Source " +"SFM Profibus ","M 1951.6 ","BOOL ","MIX - Profibus network fault " +"Signal Exchange Filler ","VAT 17 "," "," " +"Signal Gen ","FB 1721 ","FB 1721 ","MIX - Signal Gen " +"Signal_Gen_Data ","DB 943 ","FB 1721 ","MIX - TASK0 " +"SlewLimit ","FB 1702 ","FB 1702 ","MIX - Slew_Limit " +"SLIM_Block ","FC 2027 ","FC 2027 ","MIX - SLIM Block " +"SLIM_Variables ","DB 968 ","DB 968 ","MIX - SLIM Variables " +"SpeedAdjust ","FC 1906 ","FC 1906 ","MIX - Speed_Adjust " +"Statistical_Analisys ","FB 1704 ","FB 1704 ","MIX - Statistical Analisys " +"StatusScroll_Time ","T 143 ","TIMER ","MIX - StatusScroll " +"SyrAutoCorrBlink2Sec ","M 1906.5 ","BOOL ","MIX - FP******* " +"SyrAutoCorrLatch ","M 1905.7 ","BOOL ","MIX - FP ****** " +"SyrAutoCorrReq ","M 1905.6 ","BOOL ","MIX - FP ****** " +"SyrBrix Autocorrection ","FB 1809 ","FB 1809 ","MIX - SyrBrix Autocorrection " +"SyrBrix_SyrupCorrPerc ","FC 1809 ","FC 1809 ","MIX - Syrup Corr Perc " +"SyrLast400Liters ","M 2002.0 ","BOOL ","From Syrup Room last 400 liter " +"Syrup Line MFM Prep ","FB 1810 ","FB 1810 ","MIX - Syrup Line MFM Prep " +"Syrup Line MFM Prep DAR ","FB 1813 ","FB 1813 "," " +"Syrup Line MFM Prep_Seq ","FC 1810 ","FC 1810 ","MIX - SyrLineMFMPrep Sequencer " +"Syrup MFM StartUp ","FB 1811 ","FB 1811 ","MIX - Syrup MFM StartUp " +"Syrup MFM StartUp_Seq ","FC 1811 ","FC 1811 ","MIX - Syrup MFM StartUp Sequencer " +"Syrup Rinse QCO_Seq ","FC 1826 ","FC 1826 "," " +"Syrup RunOut ","FB 1812 ","FB 1812 ","MIX - Syrup RunOut " +"SyrupDensity ","FC 1907 ","FC 1907 ","MIX - Syrup_Density " +"SyrupLineRinse ","FB 1828 ","FB 1828 "," " +"SyrupRoomCtrl ","FC 2024 ","FC 2024 ","MIX - Syrup Room Ctrl " +"SyrupRoomCtrl_UpdateVal ","FC 2031 ","FC 2031 ","MIX - SyrupRoomCtrl_UpdateVal " +"SyrupRunOutReset ","M 1904.6 ","BOOL ","MIX - FP ****** " +"System_Run_Out ","FB 1820 ","FB 1820 ","MIX - System_Run_Out " +"System_Run_Out_Data ","DB 920 ","FB 1820 ","MIX - System_Run_Out_Data " +"System_RunOut_Variables ","DB 962 ","DB 962 ","MIX - System Run Out Variables " +"T_Timer ","FC 40 ","FC 40 ","Total Timer " +"T019 ","T 19 ","TIMER ","HMI Global DP Diagnosis Reset Delay " +"T105 ","T 105 ","TIMER ","MIX - Clock Signal " +"T121 ","T 121 ","TIMER ","Timer OFF Maselli Alarm after first prod " +"T130 ","T 130 ","TIMER ","Timer ON Delay closing AVN302 " +"T131 ","T 131 ","TIMER ","Timer ON Delay closing AVN303 " +"T132 ","T 132 ","TIMER ","Timer Off step 5 " +"T141 ","T 141 ","TIMER ","MIX - Profibus_DP_Diagnostic " +"T169 ","T 169 ","TIMER ","Syrup Pump Preparation Delay " +"T184 ","T 184 ","TIMER ","Start Up with Flooding Pumps Delay " +"t185 ","T 185 ","TIMER ","Start Up with Flooding: Reset Delay " +"T90 ","T 90 ","TIMER ","Delay Filler Bottle in Filling " +"TADDR_PAR ","UDT 66 ","UDT 66 ","Address and Length of Sendbuffer for TUSEND or of Receivebuffer for TURCV " +"TankLevel ","FC 2011 ","FC 2011 ","MIX - Tank Level Mixer " +"TankLevelToHeight ","FC 1839 ","FC 1839 ","MIX - TankLevelToHeight " +"TCON ","FB 65 ","FB 65 ","Connect " +"TCON_PAR ","UDT 65 ","UDT 65 ","Connection Parameters for TCON " +"TDISCON ","FB 66 ","FB 66 ","Disconnect " +"Test ","M 200.0 ","BOOL "," " +"TEST_DB ","SFC 24 ","SFC 24 ","Test Data Block " +"TestBool ","M 200.1 ","BOOL "," " +"TIM_S5TI ","FC 140 ","FC 140 ","Total Timer " +"Time_250ms ","MD 1956 ","REAL ","MIX - 250ms Task (Blender Ctrl Main) " +"Time_300ms ","MD 1960 ","REAL ","MIX - 300ms Task (PID) " +"Time_50ms ","MD 1952 ","REAL ","MIX - 50ms Task (Void) " +"Time_rins_syrup ","T 168 ","TIMER ","Delay open valvs " +"TIME_TCK ","SFC 64 ","SFC 64 ","MIX - Read the System Time " +"TOF ","SFB 5 ","SFB 5 ","Generate an Off Delay " +"TON ","SFB 4 ","SFB 4 ","Generate an On Delay " +"TP ","SFB 3 ","SFB 3 ","Generate a Pulse " +"TRCV ","FB 64 ","FB 64 ","Receive Data " +"TSEND ","FB 63 ","FB 63 ","Send Data " +"TURCV ","FB 68 ","FB 68 ","Receive Data via UDP " +"TUSEND ","FB 67 ","FB 67 ","Send Data via UDP " +"UDP FB_ALM_RECEIVE ","FB 13 ","FB 13 ","FB FOR CONTROL THE RECEIVE ON NETWORK -UDP Comunication " +"UDP FB_ALM_SEND ","FB 12 ","FB 12 ","FB FOR CONTROL THE SEND ON NETWORK -UDP Comunication " +"UR29 ","PEW 300 ","WORD ","UR62 PEW 4.20ma " +"UR62 ","PEW 304 ","WORD ","UR29 PEW 4.20ma " +"VacuumCtrl ","FC 2023 ","FC 2023 ","MIX - Vacuum Ctrl " +"VacuumCtrl_Temperature ","FC 2039 ","FC 2039 ","MIX - Vacuum Ctrl - Temperature Ctrl " +"ValveFlow ","FC 2043 ","FC 2043 ","MIX - Valve_Flow " +"VAT_AntonPaar ","VAT 9 "," "," " +"VAT_Blendfill Simulation","VAT 8 "," "," " +"VAT_CO2 ","VAT 21 "," "," " +"VAT_DAR_PEW_TEST ","VAT 24 "," "," " +"VAT_Flowmeter ","VAT 7 "," "," " +"VAT_ICS FANO ","VAT 19 "," "," " +"VAT_ICSTest ","VAT 12 "," "," " +"VAT_ICSTest2 ","VAT 13 "," "," " +"VAT_ICSTest3 ","VAT 16 "," "," " +"VAT_ICSWaitEventReq ","VAT 14 "," "," " +"VAT_ICSWaitEventStatus ","VAT 15 "," "," " +"VAT_Ingressi_Analogici ","VAT 3 "," "," " +"VAT_Ingressi_Digitali ","VAT 2 "," "," " +"VAT_Ingressi_Uscite_CIP ","VAT 18 "," "," " +"VAT_Inverter ","VAT 4 "," "," " +"VAT_Inverter2 ","VAT 6 "," "," " +"VAT_QCO Timing ","VAT 20 "," "," " +"VAT_Simulazione ","VAT 1 "," "," " +"VAT_Simulazione2 ","VAT 5 "," "," " +"VAT_STEP_00 ","VAT 23 "," "," " +"VAT_ValveTest ","VAT 10 "," "," " +"WaterDensity ","FC 1950 ","FC 1950 ","MIX - Water Density " +"WR_REC ","SFC 58 ","SFC 58 ","MIX - Write Data Record " +"WRIT_DBL ","SFC 84 ","SFC 84 ","Write Data Block in Load Memory " +"WritePeripheral ","FC 1970 ","FC 1970 ","MIX - Write Analog Data " diff --git a/application_events.json b/application_events.json index 26eec2e..1753a39 100644 --- a/application_events.json +++ b/application_events.json @@ -522,8 +522,311 @@ "event_type": "csv_cleanup_failed", "message": "CSV cleanup failed: 'max_hours'", "details": {} + }, + { + "timestamp": "2025-08-14T13:41:49.859880", + "level": "info", + "event_type": "application_started", + "message": "Application initialization completed successfully", + "details": {} + }, + { + "timestamp": "2025-08-14T13:41:49.893768", + "level": "info", + "event_type": "dataset_activated", + "message": "Dataset activated: DAR", + "details": { + "dataset_id": "DAR", + "variables_count": 2, + "streaming_count": 2, + "prefix": "gateway_phoenix" + } + }, + { + "timestamp": "2025-08-14T13:41:49.894887", + "level": "info", + "event_type": "csv_recording_started", + "message": "CSV recording started: 1 datasets activated", + "details": { + "activated_datasets": 1, + "total_datasets": 3 + } + }, + { + "timestamp": "2025-08-14T13:41:49.926928", + "level": "error", + "event_type": "csv_cleanup_failed", + "message": "CSV cleanup failed: 'max_hours'", + "details": {} + }, + { + "timestamp": "2025-08-14T13:46:38.876812", + "level": "info", + "event_type": "application_started", + "message": "Application initialization completed successfully", + "details": {} + }, + { + "timestamp": "2025-08-14T13:46:38.925616", + "level": "info", + "event_type": "dataset_activated", + "message": "Dataset activated: DAR", + "details": { + "dataset_id": "DAR", + "variables_count": 2, + "streaming_count": 2, + "prefix": "gateway_phoenix" + } + }, + { + "timestamp": "2025-08-14T13:46:38.926826", + "level": "info", + "event_type": "csv_recording_started", + "message": "CSV recording started: 1 datasets activated", + "details": { + "activated_datasets": 1, + "total_datasets": 3 + } + }, + { + "timestamp": "2025-08-14T13:46:38.957730", + "level": "error", + "event_type": "csv_cleanup_failed", + "message": "CSV cleanup failed: 'max_hours'", + "details": {} + }, + { + "timestamp": "2025-08-14T14:00:00.330129", + "level": "error", + "event_type": "csv_cleanup_failed", + "message": "CSV cleanup failed: 'max_hours'", + "details": {} + }, + { + "timestamp": "2025-08-14T14:17:45.030946", + "level": "info", + "event_type": "application_started", + "message": "Application initialization completed successfully", + "details": {} + }, + { + "timestamp": "2025-08-14T14:17:45.078942", + "level": "info", + "event_type": "dataset_activated", + "message": "Dataset activated: DAR", + "details": { + "dataset_id": "DAR", + "variables_count": 2, + "streaming_count": 2, + "prefix": "gateway_phoenix" + } + }, + { + "timestamp": "2025-08-14T14:17:45.080942", + "level": "info", + "event_type": "csv_recording_started", + "message": "CSV recording started: 1 datasets activated", + "details": { + "activated_datasets": 1, + "total_datasets": 3 + } + }, + { + "timestamp": "2025-08-14T14:17:45.113420", + "level": "error", + "event_type": "csv_cleanup_failed", + "message": "CSV cleanup failed: 'max_hours'", + "details": {} + }, + { + "timestamp": "2025-08-14T14:29:48.098633", + "level": "info", + "event_type": "application_started", + "message": "Application initialization completed successfully", + "details": {} + }, + { + "timestamp": "2025-08-14T14:29:48.148327", + "level": "info", + "event_type": "dataset_activated", + "message": "Dataset activated: DAR", + "details": { + "dataset_id": "DAR", + "variables_count": 2, + "streaming_count": 2, + "prefix": "gateway_phoenix" + } + }, + { + "timestamp": "2025-08-14T14:29:48.149336", + "level": "info", + "event_type": "csv_recording_started", + "message": "CSV recording started: 1 datasets activated", + "details": { + "activated_datasets": 1, + "total_datasets": 3 + } + }, + { + "timestamp": "2025-08-14T14:29:48.181610", + "level": "error", + "event_type": "csv_cleanup_failed", + "message": "CSV cleanup failed: 'max_hours'", + "details": {} + }, + { + "timestamp": "2025-08-14T14:31:49.061274", + "level": "info", + "event_type": "udp_streaming_started", + "message": "UDP streaming to PlotJuggler started", + "details": { + "udp_host": "127.0.0.1", + "udp_port": 9870, + "datasets_available": 3 + } + }, + { + "timestamp": "2025-08-14T14:38:28.137719", + "level": "info", + "event_type": "application_started", + "message": "Application initialization completed successfully", + "details": {} + }, + { + "timestamp": "2025-08-14T14:38:28.200071", + "level": "info", + "event_type": "dataset_activated", + "message": "Dataset activated: DAR", + "details": { + "dataset_id": "DAR", + "variables_count": 2, + "streaming_count": 2, + "prefix": "gateway_phoenix" + } + }, + { + "timestamp": "2025-08-14T14:38:28.202181", + "level": "info", + "event_type": "csv_recording_started", + "message": "CSV recording started: 1 datasets activated", + "details": { + "activated_datasets": 1, + "total_datasets": 3 + } + }, + { + "timestamp": "2025-08-14T14:38:28.205203", + "level": "info", + "event_type": "udp_streaming_started", + "message": "UDP streaming to PlotJuggler started", + "details": { + "udp_host": "127.0.0.1", + "udp_port": 9870, + "datasets_available": 3 + } + }, + { + "timestamp": "2025-08-14T14:38:28.232863", + "level": "error", + "event_type": "csv_cleanup_failed", + "message": "CSV cleanup failed: 'max_hours'", + "details": {} + }, + { + "timestamp": "2025-08-14T14:42:54.688764", + "level": "info", + "event_type": "application_started", + "message": "Application initialization completed successfully", + "details": {} + }, + { + "timestamp": "2025-08-14T14:42:54.737860", + "level": "info", + "event_type": "dataset_activated", + "message": "Dataset activated: DAR", + "details": { + "dataset_id": "DAR", + "variables_count": 2, + "streaming_count": 2, + "prefix": "gateway_phoenix" + } + }, + { + "timestamp": "2025-08-14T14:42:54.739862", + "level": "info", + "event_type": "csv_recording_started", + "message": "CSV recording started: 1 datasets activated", + "details": { + "activated_datasets": 1, + "total_datasets": 3 + } + }, + { + "timestamp": "2025-08-14T14:42:54.740875", + "level": "info", + "event_type": "udp_streaming_started", + "message": "UDP streaming to PlotJuggler started", + "details": { + "udp_host": "127.0.0.1", + "udp_port": 9870, + "datasets_available": 3 + } + }, + { + "timestamp": "2025-08-14T14:42:54.771400", + "level": "error", + "event_type": "csv_cleanup_failed", + "message": "CSV cleanup failed: 'max_hours'", + "details": {} + }, + { + "timestamp": "2025-08-14T14:47:13.179212", + "level": "info", + "event_type": "application_started", + "message": "Application initialization completed successfully", + "details": {} + }, + { + "timestamp": "2025-08-14T14:47:13.212383", + "level": "info", + "event_type": "dataset_activated", + "message": "Dataset activated: DAR", + "details": { + "dataset_id": "DAR", + "variables_count": 2, + "streaming_count": 2, + "prefix": "gateway_phoenix" + } + }, + { + "timestamp": "2025-08-14T14:47:13.213646", + "level": "info", + "event_type": "csv_recording_started", + "message": "CSV recording started: 1 datasets activated", + "details": { + "activated_datasets": 1, + "total_datasets": 3 + } + }, + { + "timestamp": "2025-08-14T14:47:13.217027", + "level": "info", + "event_type": "udp_streaming_started", + "message": "UDP streaming to PlotJuggler started", + "details": { + "udp_host": "127.0.0.1", + "udp_port": 9870, + "datasets_available": 3 + } + }, + { + "timestamp": "2025-08-14T14:47:13.244962", + "level": "error", + "event_type": "csv_cleanup_failed", + "message": "CSV cleanup failed: 'max_hours'", + "details": {} } ], - "last_updated": "2025-08-14T13:31:23.939267", - "total_entries": 49 + "last_updated": "2025-08-14T14:47:13.244962", + "total_entries": 82 } \ No newline at end of file diff --git a/config/data/dataset_variables.json b/config/data/dataset_variables.json index cff7e7c..c87504d 100644 --- a/config/data/dataset_variables.json +++ b/config/data/dataset_variables.json @@ -4,20 +4,20 @@ "dataset_id": "DAR", "variables": [ { - "name": "UR29_Brix", "area": "db", "db": 1011, + "name": "UR29_Brix", "offset": 1322, - "type": "real", - "streaming": true + "streaming": true, + "type": "real" }, { - "name": "UR29_ma", "area": "db", "db": 1011, + "name": "UR29_ma", "offset": 1296, - "type": "real", - "streaming": true + "streaming": true, + "type": "real" } ] }, diff --git a/config/data/plot_variables.json b/config/data/plot_variables.json index d0ff560..3fac8b2 100644 --- a/config/data/plot_variables.json +++ b/config/data/plot_variables.json @@ -4,20 +4,20 @@ "plot_id": "plot_1", "variables": [ { - "variable_name": "UR29_Brix", - "label": "Brix", "color": "#3498db", + "enabled": true, + "label": "Brix", "line_width": 2, - "y_axis": "left", - "enabled": true + "variable_name": "UR29_Brix", + "y_axis": "left" }, { - "variable_name": "UR29_ma", - "label": "ma", "color": "#e74c3c", + "enabled": true, + "label": "ma", "line_width": 2, - "y_axis": "left", - "enabled": true + "variable_name": "UR29_ma", + "y_axis": "left" } ] } diff --git a/config/schema/ui/layout-demo.uischema.json b/config/schema/ui/layout-demo.uischema.json deleted file mode 100644 index c10863d..0000000 --- a/config/schema/ui/layout-demo.uischema.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "ui:title": "Enhanced UI Schema Layout Demo", - "ui:description": "This demo showcases the full UI schema layout capabilities of the enhanced RJSF implementation", - "ui:layout": [ - [ - { - "name": "basic_text", - "width": 6 - }, - { - "name": "updown_number", - "width": 3 - }, - { - "name": "enabled_checkbox", - "width": 3 - } - ], - [ - { - "name": "long_description", - "width": 12 - } - ], - [ - { - "name": "dropdown_selection", - "width": 4 - }, - { - "name": "variable_selector", - "width": 4 - }, - { - "name": "readonly_field", - "width": 4 - } - ] - ], - "ui:order": [ - "basic_text", - "updown_number", - "enabled_checkbox", - "long_description", - "dropdown_selection", - "variable_selector", - "readonly_field" - ], - "basic_text": { - "ui:placeholder": "Enter some text here...", - "ui:help": "This is a standard text input with placeholder and help text" - }, - "updown_number": { - "ui:widget": "updown", - "ui:help": "Use +/- buttons to adjust the value" - }, - "enabled_checkbox": { - "ui:widget": "checkbox", - "ui:help": "Toggle this setting on/off" - }, - "long_description": { - "ui:widget": "textarea", - "ui:placeholder": "Enter a longer description here...", - "ui:help": "Multi-line text area that spans the full width" - }, - "dropdown_selection": { - "ui:widget": "select", - "ui:help": "Choose an option from the dropdown" - }, - "variable_selector": { - "ui:widget": "VariableSelectorWidget", - "ui:help": "Select a variable from the available PLC variables" - }, - "readonly_field": { - "ui:readonly": true, - "ui:help": "This field is read-only and cannot be edited" - } -} diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 78fc3bc..3908659 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -2,12 +2,7 @@ import React from 'react' import recLogo from './assets/logo/record.png' import { Routes, Route, Link } from 'react-router-dom' import { Box, Container, Flex, HStack, Select, Button, Heading, Text, useColorMode, useColorModeValue, Stack } from '@chakra-ui/react' -import EventsPage from './pages/Events.jsx' -import ConfigPage from './pages/Config.jsx' -import PlotsPage from './pages/Plots.jsx' -import PLCConfigModal from './components/PLCConfigModal.jsx' -import DashboardPage from './pages/DashboardNew.jsx' -import DatasetManager from './components/DatasetManager.jsx' +import DashboardPage from './pages/Dashboard.jsx' function Home() { return ( @@ -85,10 +80,6 @@ function NavBar() { PLC Streamer - - - - @@ -98,16 +89,12 @@ function NavBar() { } function App() { - const [showPLCModal, setShowPLCModal] = React.useState(false) return ( } /> - } /> - } /> - } /> - } /> + } /> ) diff --git a/frontend/src/components/DatasetManager.jsx b/frontend/src/components/DatasetManager.jsx deleted file mode 100644 index cac4c70..0000000 --- a/frontend/src/components/DatasetManager.jsx +++ /dev/null @@ -1,287 +0,0 @@ -import React, { useState, useEffect } from 'react' -import { - Box, Container, Heading, HStack, VStack, Button, Tabs, TabList, TabPanels, Tab, TabPanel, - Card, CardBody, CardHeader, Grid, GridItem, Text, Badge, Alert, AlertIcon, - useColorModeValue, Flex, Spacer, IconButton, useToast -} from '@chakra-ui/react' -import Form from '@rjsf/chakra-ui' -import validator from '@rjsf/validator-ajv8' -import { getSchema, readConfig, writeConfig } from '../services/api.js' -import LayoutObjectFieldTemplate from './rjsf/LayoutObjectFieldTemplate.jsx' -import DatasetVariableManager from './DatasetVariableManager.jsx' - -export default function DatasetManager() { - const [datasetsSchema, setDatasetsSchema] = useState(null) - const [datasetsUiSchema, setDatasetsUiSchema] = useState(null) - const [datasetsData, setDatasetsData] = useState(null) - - const [variablesSchema, setVariablesSchema] = useState(null) - const [variablesUiSchema, setVariablesUiSchema] = useState(null) - const [variablesData, setVariablesData] = useState(null) - - const [loading, setLoading] = useState(true) - const [selectedDatasetId, setSelectedDatasetId] = useState(null) - - const toast = useToast() - const cardBg = useColorModeValue('white', 'gray.700') - const borderColor = useColorModeValue('gray.200', 'gray.600') - - useEffect(() => { - loadData() - }, []) - - const loadData = async () => { - setLoading(true) - try { - const [ - datasetsSchemaResp, datasetsDataResp, - variablesSchemaResp, variablesDataResp - ] = await Promise.all([ - getSchema('dataset-definitions'), - readConfig('dataset-definitions'), - getSchema('dataset-variables'), - readConfig('dataset-variables') - ]) - - setDatasetsSchema(datasetsSchemaResp.schema) - setDatasetsUiSchema(datasetsSchemaResp.ui_schema) - setDatasetsData(datasetsDataResp.data) - - setVariablesSchema(variablesSchemaResp.schema) - setVariablesUiSchema(variablesSchemaResp.ui_schema) - setVariablesData(variablesDataResp.data) - - // Set first dataset as selected if none selected - if (datasetsDataResp.data?.datasets && !selectedDatasetId) { - const firstDataset = Object.keys(datasetsDataResp.data.datasets)[0] - setSelectedDatasetId(firstDataset) - } - } catch (error) { - toast({ - title: 'Error loading data', - description: error.message, - status: 'error', - duration: 5000, - isClosable: true - }) - } finally { - setLoading(false) - } - } - - const handleDatasetsSave = async ({ formData }) => { - try { - await writeConfig('dataset-definitions', formData) - setDatasetsData(formData) - toast({ - title: 'Dataset definitions saved', - status: 'success', - duration: 3000, - isClosable: true - }) - } catch (error) { - toast({ - title: 'Error saving datasets', - description: error.message, - status: 'error', - duration: 5000, - isClosable: true - }) - } - } - - const handleVariablesSave = async ({ formData }) => { - try { - await writeConfig('dataset-variables', formData) - setVariablesData(formData) - toast({ - title: 'Dataset variables saved', - status: 'success', - duration: 3000, - isClosable: true - }) - } catch (error) { - toast({ - title: 'Error saving variables', - description: error.message, - status: 'error', - duration: 5000, - isClosable: true - }) - } - } - - if (loading) { - return ( - - Loading dataset manager... - - ) - } - - return ( - - - - 📊 Dataset Manager - - - - - - - - - 📋 Dataset Definitions - ⚙️ Dataset Variables - 🔧 Variable Manager - - - - - - - Dataset Metadata Configuration - - Configure dataset names, prefixes, sampling intervals and enable/disable datasets - - - - {datasetsSchema && ( -
setDatasetsData(formData)} - uiSchema={datasetsUiSchema} - templates={{ ObjectFieldTemplate: LayoutObjectFieldTemplate }} - > - - - - -
- )} -
-
-
- - - - - Dataset Variables Configuration - - Raw JSON configuration for variables assigned to each dataset - - - - {variablesSchema && ( -
setVariablesData(formData)} - uiSchema={variablesUiSchema} - templates={{ ObjectFieldTemplate: LayoutObjectFieldTemplate }} - > - - - - -
- )} -
-
-
- - - { - const updatedData = { - ...variablesData, - dataset_variables: newVariables - } - handleVariablesSave({ formData: updatedData }) - }} - /> - -
-
-
-
- ) -} - -function DatasetOverview({ datasets, variables, selectedDatasetId, onSelectDataset }) { - const cardBg = useColorModeValue('white', 'gray.700') - const selectedBg = useColorModeValue('blue.50', 'blue.900') - const borderColor = useColorModeValue('gray.200', 'gray.600') - - return ( - - 📊 Datasets Overview - - {Object.entries(datasets).map(([id, dataset]) => { - const varCount = variables[id]?.variables ? Object.keys(variables[id].variables).length : 0 - const streamingCount = variables[id]?.streaming_variables?.length || 0 - const isSelected = selectedDatasetId === id - - return ( - onSelectDataset(id)} - _hover={{ borderColor: 'blue.300' }} - > - - - - {dataset.name} - - {dataset.enabled ? 'Active' : 'Inactive'} - - - - - ID: {id} • Prefix: {dataset.prefix} - - - - - 🔧 {varCount} variables - - - 📡 {streamingCount} streaming - - - - {dataset.sampling_interval && ( - - ⏱️ {dataset.sampling_interval}s interval - - )} - - - - ) - })} - - - ) -} diff --git a/frontend/src/components/PLCConfigModal.jsx b/frontend/src/components/PLCConfigModal.jsx deleted file mode 100644 index 3febdfc..0000000 --- a/frontend/src/components/PLCConfigModal.jsx +++ /dev/null @@ -1,100 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { - Modal, - ModalOverlay, - ModalContent, - ModalHeader, - ModalBody, - ModalFooter, - ModalCloseButton, - Button, - Alert, - AlertIcon, -} from '@chakra-ui/react' -import Form from '@rjsf/chakra-ui'; -import validator from '@rjsf/validator-ajv8'; -import { getSchema, readConfig, writeConfig } from '../services/api.js'; -// Chakra theme widgets are used by default - -const uiSchema = { - plc_config: { - rack: { 'ui:widget': 'updown' }, - slot: { 'ui:widget': 'updown' }, - }, - udp_config: { - port: { 'ui:widget': 'updown' }, - }, - sampling_interval: { 'ui:widget': 'updown' }, - csv_config: { - max_size_mb: { 'ui:widget': 'updown' }, - max_days: { 'ui:widget': 'updown' }, - max_hours: { 'ui:widget': 'updown' }, - cleanup_interval_hours: { 'ui:widget': 'updown' }, - }, -}; - -export default function PLCConfigModal({ show, onClose }) { - const [schema, setSchema] = useState(null); - const [formData, setFormData] = useState(null); - const [saving, setSaving] = useState(false); - const [msg, setMsg] = useState(''); - - useEffect(() => { - if (show) { - Promise.all([getSchema('plc'), readConfig('plc')]) - .then(([s, d]) => { - setSchema(s.schema); - setFormData(d.data); - }) - .catch(() => setMsg('Error loading PLC config')); - } - }, [show]); - - const handleSubmit = async ({ formData: newData }) => { - setSaving(true); - setMsg(''); - try { - await writeConfig('plc', newData); - setMsg('Saved successfully'); - onClose?.(); - } catch (e) { - setMsg(e.message || 'Error saving PLC config'); - } finally { - setSaving(false); - } - }; - - return ( - - - - PLC Configuration - - - {msg && ( - - {msg} - - )} - {schema && ( -
setFormData(formData)} - uiSchema={uiSchema} - > - -
- )} -
- - - -
-
- ); -} - - diff --git a/frontend/src/components/PlotManager.jsx b/frontend/src/components/PlotManager.jsx index 6843c7f..63231da 100644 --- a/frontend/src/components/PlotManager.jsx +++ b/frontend/src/components/PlotManager.jsx @@ -29,7 +29,15 @@ import { Tab, TabPanel, Divider, - Select + Select, + Accordion, + AccordionItem, + AccordionButton, + AccordionPanel, + AccordionIcon, + Collapse, + useDisclosure, + Spinner } from '@chakra-ui/react' import Form from '@rjsf/chakra-ui' import validator from '@rjsf/validator-ajv8' @@ -39,6 +47,97 @@ import PlotRealtimeSession from './PlotRealtimeSession' import { useVariableContext } from '../contexts/VariableContext' import * as api from '../services/api' +// Collapsible Form Component for Plot Definitions +function CollapsiblePlotForm({ data, schema, uiSchema, onSave, title, icon, getItemLabel }) { + const [isOpen, setIsOpen] = useState(false) + const [formData, setFormData] = useState(data) + + useEffect(() => { + setFormData(data) + }, [data]) + + if (!schema || !formData) { + return ( + + + + + Loading {title}... + + + + ) + } + + const items = formData[Object.keys(formData)[0]] || [] + + return ( + + + + + {icon} {title} + + {items.length} item{items.length !== 1 ? 's' : ''} configured + + + + + + {/* Show summary when collapsed */} + {!isOpen && items.length > 0 && ( + + Quick Overview: + + {items.slice(0, 5).map((item, index) => ( + + {getItemLabel ? getItemLabel(item) : (item.name || item.id || `Item ${index + 1}`)} + + ))} + {items.length > 5 && ( + + +{items.length - 5} more... + + )} + + + )} + + + + +
onSave(formData)} + onChange={({ formData }) => setFormData(formData)} + > + + + + +
+
+
+
+ ) +} + // Pure RJSF Plot Manager Component export default function PlotManager() { const { triggerVariableRefresh } = useVariableContext() @@ -310,59 +409,26 @@ export default function PlotManager() { {/* RJSF Configuration Forms */} - - - 📋 Plot Definitions - ⚙️ Plot Variables - + + {/* Plot Definitions - Collapsible */} + `${item.name || item.id} (${item.time_window || 60}s)`} + /> - - - {plotsSchemaData?.schema && plotsConfig && ( - - - Plot Session Definitions - - Configure plot sessions, time windows, triggers and visual settings - - - -
savePlotsConfig(formData)} - onChange={({ formData }) => setPlotsConfig(formData)} - > - - - - -
-
-
- )} -
- - - {/* Plot Variables Configuration with Combo Selector - Type 3 Pattern */} - - - Plot Variables Configuration - - Select a plot session, then configure which variables are displayed in that plot + {/* Plot Variables Configuration */} + + + ⚙️ Plot Variables Configuration + + Select a plot session, then configure its variables and visual settings + + diff --git a/frontend/src/components/PlotManager.jsx.backup b/frontend/src/components/PlotManager.jsx.backup new file mode 100644 index 0000000..63231da --- /dev/null +++ b/frontend/src/components/PlotManager.jsx.backup @@ -0,0 +1,611 @@ +import React, { useState, useEffect, useCallback } from 'react' +import { + Box, + Card, + CardBody, + CardHeader, + Button, + Text, + Grid, + Flex, + Spacer, + HStack, + VStack, + useColorModeValue, + useToast, + Heading, + Table, + Thead, + Tbody, + Tr, + Th, + Td, + TableContainer, + Badge, + IconButton, + Tabs, + TabList, + TabPanels, + Tab, + TabPanel, + Divider, + Select, + Accordion, + AccordionItem, + AccordionButton, + AccordionPanel, + AccordionIcon, + Collapse, + useDisclosure, + Spinner +} from '@chakra-ui/react' +import Form from '@rjsf/chakra-ui' +import validator from '@rjsf/validator-ajv8' +import allWidgets from './widgets/AllWidgets' +import LayoutObjectFieldTemplate from './rjsf/LayoutObjectFieldTemplate' +import PlotRealtimeSession from './PlotRealtimeSession' +import { useVariableContext } from '../contexts/VariableContext' +import * as api from '../services/api' + +// Collapsible Form Component for Plot Definitions +function CollapsiblePlotForm({ data, schema, uiSchema, onSave, title, icon, getItemLabel }) { + const [isOpen, setIsOpen] = useState(false) + const [formData, setFormData] = useState(data) + + useEffect(() => { + setFormData(data) + }, [data]) + + if (!schema || !formData) { + return ( + + + + + Loading {title}... + + + + ) + } + + const items = formData[Object.keys(formData)[0]] || [] + + return ( + + + + + {icon} {title} + + {items.length} item{items.length !== 1 ? 's' : ''} configured + + + + + + {/* Show summary when collapsed */} + {!isOpen && items.length > 0 && ( + + Quick Overview: + + {items.slice(0, 5).map((item, index) => ( + + {getItemLabel ? getItemLabel(item) : (item.name || item.id || `Item ${index + 1}`)} + + ))} + {items.length > 5 && ( + + +{items.length - 5} more... + + )} + + + )} + + + + +
onSave(formData)} + onChange={({ formData }) => setFormData(formData)} + > + + + + +
+
+
+
+ ) +} + +// Pure RJSF Plot Manager Component +export default function PlotManager() { + const { triggerVariableRefresh } = useVariableContext() + const [plots, setPlots] = useState({}) + const [plotsSchemaData, setPlotsSchemaData] = useState(null) + const [plotsVariablesSchemaData, setPlotsVariablesSchemaData] = useState(null) + const [plotsConfig, setPlotsConfig] = useState(null) + const [plotsVariablesConfig, setPlotsVariablesConfig] = useState(null) + const [selectedPlotId, setSelectedPlotId] = useState('') + const [loading, setLoading] = useState(true) + const [actionLoading, setActionLoading] = useState({}) + + const toast = useToast() + const cardBg = useColorModeValue('white', 'gray.700') + const borderColor = useColorModeValue('gray.200', 'gray.600') + + const setActionState = (key, loading) => { + setActionLoading(prev => ({ ...prev, [key]: loading })) + } + + const loadPlotData = useCallback(async () => { + try { + setLoading(true) + const [ + plotsData, + plotsSchemaResponse, + plotsVariablesSchemaResponse, + plotsConfigData, + plotsVariablesConfigData + ] = await Promise.all([ + api.getPlots(), + api.getSchema('plot-definitions'), + api.getSchema('plot-variables'), + api.readConfig('plot-definitions'), + api.readConfig('plot-variables') + ]) + + setPlots(plotsData?.plots || {}) + setPlotsSchemaData(plotsSchemaResponse) + setPlotsVariablesSchemaData(plotsVariablesSchemaResponse) + setPlotsConfig(plotsConfigData) + setPlotsVariablesConfig(plotsVariablesConfigData) + + // Auto-select first plot if none selected + if (!selectedPlotId && plotsConfigData?.plots?.length > 0) { + setSelectedPlotId(plotsConfigData.plots[0].id) + } + } catch (error) { + toast({ + title: '❌ Failed to load plot data', + description: error.message, + status: 'error', + duration: 3000 + }) + } finally { + setLoading(false) + } + }, [toast]) + + // Helper function to get plot definitions from config + const getPlotDefinitions = () => { + return plotsConfig?.plots || [] + } + + // Helper function to get variables for a specific plot + const getPlotVariables = (plotId) => { + const plotVarsConfig = plotsVariablesConfig?.variables || [] + const plotVarEntry = plotVarsConfig.find(entry => entry.plot_id === plotId) + return plotVarEntry?.variables || [] + } + + // Type 3 Pattern Helper Functions + // Get filtered variables for selected plot + const getSelectedPlotVariables = () => { + if (!plotsVariablesConfig?.variables || !selectedPlotId) { + return { variables: [] } + } + + const plotVars = plotsVariablesConfig.variables.find(v => v.plot_id === selectedPlotId) + return plotVars || { variables: [] } + } + + // Update variables for selected plot + const updateSelectedPlotVariables = (newVariableData) => { + if (!plotsVariablesConfig?.variables || !selectedPlotId) return + + const updatedVariables = plotsVariablesConfig.variables.map(v => + v.plot_id === selectedPlotId + ? { ...v, ...newVariableData } + : v + ) + + // If plot not found, add new entry + if (!plotsVariablesConfig.variables.find(v => v.plot_id === selectedPlotId)) { + updatedVariables.push({ + plot_id: selectedPlotId, + ...newVariableData + }) + } + + const updatedConfig = { ...plotsVariablesConfig, variables: updatedVariables } + setPlotsVariablesConfig(updatedConfig) + } + + // Available plots for combo selector + const availablePlots = plotsConfig?.plots || [] + + // Handle plot configuration updates + const handlePlotConfigUpdate = async (plotId, newConfig) => { + try { + // Update the plot definition in local state + const updatedPlots = getPlotDefinitions().map(plot => + plot.id === plotId ? { ...plot, ...newConfig } : plot + ) + + const updatedConfig = { ...plotsConfig, plots: updatedPlots } + await savePlotsConfig(updatedConfig) + + // Reload data to get fresh state + await loadPlotData() + } catch (error) { + throw new Error(`Failed to update plot configuration: ${error.message}`) + } + } + + // Handle plot removal + const handlePlotRemove = async (plotId) => { + try { + // Remove from plot definitions + const updatedPlots = getPlotDefinitions().filter(plot => plot.id !== plotId) + const updatedPlotsConfig = { ...plotsConfig, plots: updatedPlots } + + // Remove from plot variables + const updatedPlotVars = (plotsVariablesConfig?.variables || []).filter( + entry => entry.plot_id !== plotId + ) + const updatedVarsConfig = { ...plotsVariablesConfig, variables: updatedPlotVars } + + // Save both configurations + await Promise.all([ + savePlotsConfig(updatedPlotsConfig), + savePlotsVariablesConfig(updatedVarsConfig) + ]) + + // Stop the plot session in backend + try { + await api.controlPlotSession(plotId, 'stop') + } catch (error) { + // Plot session may not exist, that's OK + } + + // Reload data + await loadPlotData() + + toast({ + title: '✅ Plot removed successfully', + status: 'success', + duration: 2000 + }) + } catch (error) { + toast({ + title: '❌ Failed to remove plot', + description: error.message, + status: 'error', + duration: 3000 + }) + } + } + + const savePlotsConfig = async (formData) => { + try { + setActionState('savePlots', true) + await api.writeConfig('plot-definitions', formData) + toast({ + title: '✅ Plot definitions saved', + status: 'success', + duration: 2000 + }) + setPlotsConfig(formData) + } catch (error) { + toast({ + title: '❌ Failed to save plot definitions', + description: error.message, + status: 'error', + duration: 3000 + }) + } finally { + setActionState('savePlots', false) + } + } + + const savePlotsVariablesConfig = async (formData) => { + try { + setActionState('savePlotsVariables', true) + await api.writeConfig('plot-variables', formData) + toast({ + title: '✅ Plot variables saved', + status: 'success', + duration: 2000 + }) + setPlotsVariablesConfig(formData) + // Trigger refresh of variable selectors (though they don't depend on plot vars directly) + triggerVariableRefresh() + } catch (error) { + toast({ + title: '❌ Failed to save plot variables', + description: error.message, + status: 'error', + duration: 3000 + }) + } finally { + setActionState('savePlotsVariables', false) + } + } + + useEffect(() => { + loadPlotData() + }, [loadPlotData]) + + if (loading) { + return ( + + + Loading plot configurations... + + + ) + } + + return ( + + + 📈 Plot Manager + + + + + {/* Active Plot Sessions with Real Chart.js Plots */} + + + 🎛️ Active Plot Sessions + + Real-time Chart.js plots with streaming data from PLC + + + + {getPlotDefinitions().length === 0 ? ( + + No plot sessions configured. Create plot definitions below to get started. + + ) : ( + + {getPlotDefinitions().map((plotDef) => ( + + ))} + + )} + + + + + + {/* RJSF Configuration Forms */} + + {/* Plot Definitions - Collapsible */} + `${item.name || item.id} (${item.time_window || 60}s)`} + /> + + {/* Plot Variables Configuration */} + + + ⚙️ Plot Variables Configuration + + Select a plot session, then configure its variables and visual settings + + + + + + {/* Step 1: Plot Selector (Combo) */} + + + + 🎯 Select Plot Session + + + {availablePlots.length === 0 && ( + + ⚠️ No plot sessions available. Configure plot definitions first in the "Plot Definitions" tab. + + )} + + + {/* Variables Configuration Form */} + {selectedPlotId && ( + + + + ⚙️ Configure Variables for Plot "{selectedPlotId}" + + + {/* Simplified schema for selected plot variables */} + {(() => { + const selectedPlotVars = getSelectedPlotVariables() + + // Schema for this plot's variables + const singlePlotSchema = { + type: "object", + properties: { + variables: { + type: "array", + title: "Variables", + description: `Variables to display in plot ${selectedPlotId}`, + items: { + type: "object", + title: "Plot Variable", + properties: { + variable_name: { + type: "string", + title: "Variable Name", + description: "Select variable from datasets with search and metadata" + }, + label: { + type: "string", + title: "Display Label", + description: "Label shown in the plot legend" + }, + color: { + type: "string", + title: "Line Color", + default: "#3182CE" + }, + line_width: { + type: "number", + title: "Line Width", + default: 2, + minimum: 1, + maximum: 10 + }, + y_axis: { + type: "string", + title: "Y-Axis", + enum: ["left", "right"], + default: "left" + } + }, + required: ["variable_name", "label"] + } + } + } + } + + const singlePlotUiSchema = { + variables: { + items: { + "ui:layout": [[ + { "name": "variable_name", "width": 4 }, + { "name": "label", "width": 2 }, + { "name": "color", "width": 2 }, + { "name": "line_width", "width": 2 }, + { "name": "y_axis", "width": 2 } + ]], + variable_name: { + "ui:widget": "variableSelector", + "ui:placeholder": "Search and select variable from datasets...", + "ui:help": "🔍 Search variables from configured datasets with live values and metadata" + }, + label: { + "ui:placeholder": "Chart legend label..." + }, + color: { + "ui:widget": "color" + }, + line_width: { + "ui:widget": "updown" + } + } + } + } + + return ( +
{ + updateSelectedPlotVariables(formData) + // Create updated config and save it + const updatedVariables = plotsVariablesConfig.variables?.map(v => + v.plot_id === selectedPlotId + ? { ...v, ...formData } + : v + ) || [] + + // If plot not found, add new entry + if (!plotsVariablesConfig.variables?.find(v => v.plot_id === selectedPlotId)) { + updatedVariables.push({ + plot_id: selectedPlotId, + ...formData + }) + } + + const updatedConfig = { ...plotsVariablesConfig, variables: updatedVariables } + savePlotsVariablesConfig(updatedConfig) + }} + onChange={({ formData }) => updateSelectedPlotVariables(formData)} + > + + + + +
+ ) + })()} +
+ )} + + {!selectedPlotId && availablePlots.length > 0 && ( + + + 👆 Select a plot session above to configure its variables + + + )} +
+
+
+
+
+
+ + ) +} diff --git a/frontend/src/components/PlotManagerSimple.jsx b/frontend/src/components/PlotManagerSimple.jsx new file mode 100644 index 0000000..fdfbb41 --- /dev/null +++ b/frontend/src/components/PlotManagerSimple.jsx @@ -0,0 +1,633 @@ +import React, { useState, useEffect, useCallback } from 'react' +import { + Box, + Card, + CardBody, + CardHeader, + Button, + Text, + Grid, + Flex, + Spacer, + HStack, + VStack, + useColorModeValue, + useToast, + Heading, + Badge, + Divider, + Accordion, + AccordionItem, + AccordionButton, + AccordionPanel, + Collapse, + useDisclosure, + Spinner, + Select +} from '@chakra-ui/react' +import { ChevronDownIcon, ChevronUpIcon } from '@chakra-ui/icons' +import Form from '@rjsf/chakra-ui' +import validator from '@rjsf/validator-ajv8' +import allWidgets from './widgets/AllWidgets' +import LayoutObjectFieldTemplate from './rjsf/LayoutObjectFieldTemplate' +import PlotRealtimeSession from './PlotRealtimeSession' +import { useVariableContext } from '../contexts/VariableContext' +import * as api from '../services/api' + +// Collapsible Plot Items Form - Each item in the array is individually collapsible +function CollapsiblePlotItemsForm({ data, schema, uiSchema, onSave, title, icon, getItemLabel }) { + const [formData, setFormData] = useState(data) + const [expandedItems, setExpandedItems] = useState(new Set()) + + useEffect(() => { + setFormData(data) + }, [data]) + + if (!schema || !formData) { + return ( + + + + + Loading {title}... + + + + ) + } + + const arrayKey = Object.keys(formData)[0] + const items = formData[arrayKey] || [] + + const toggleItemExpansion = (index) => { + const newExpanded = new Set(expandedItems) + if (newExpanded.has(index)) { + newExpanded.delete(index) + } else { + newExpanded.add(index) + } + setExpandedItems(newExpanded) + } + + const updateItem = (index, newItemData) => { + const newItems = [...items] + newItems[index] = newItemData + const newFormData = { ...formData, [arrayKey]: newItems } + setFormData(newFormData) + } + + const addItem = () => { + const newItem = {} + const newItems = [...items, newItem] + const newFormData = { ...formData, [arrayKey]: newItems } + setFormData(newFormData) + setExpandedItems(new Set([...expandedItems, items.length])) + } + + const removeItem = (index) => { + const newItems = items.filter((_, i) => i !== index) + const newFormData = { ...formData, [arrayKey]: newItems } + setFormData(newFormData) + // Update expanded items indices + const newExpanded = new Set() + expandedItems.forEach(i => { + if (i < index) newExpanded.add(i) + else if (i > index) newExpanded.add(i - 1) + }) + setExpandedItems(newExpanded) + } + + const saveChanges = () => { + onSave(formData) + } + + // Get item schema from the array schema + const itemSchema = schema?.properties?.[arrayKey]?.items || {} + + // Get item UI schema - extract from the nested structure + const arrayUiSchema = uiSchema?.[arrayKey] || {} + const itemUiSchema = arrayUiSchema.items || {} + + return ( + + + + + {icon} {title} + + {items.length} item{items.length !== 1 ? 's' : ''} configured + + + + + + + + + + + {items.length === 0 ? ( + + + No items configured yet + + + + ) : ( + + {items.map((item, index) => { + const isExpanded = expandedItems.has(index) + const itemLabel = getItemLabel ? getItemLabel(item) : (item.name || item.id || `Item ${index + 1}`) + + return ( + + + + + + #{index + 1} + + + + + + + +
updateItem(index, newItemData)} + > +
{/* Prevents form buttons from showing */} +
+
+
+
+ ) + })} +
+ )} +
+
+ ) +} + +// Collapsible Plot Component +function CollapsiblePlotChart({ plotDefinition, plotVariables, onConfigUpdate, onRemove }) { + const [isOpen, setIsOpen] = useState(false) + + return ( + + + + + 📈 {plotDefinition.name || plotDefinition.id} + + {plotDefinition.time_window}s window • {plotVariables?.variables?.length || 0} variables + + + + + + + + + + + + + + + + ) +} + +// Simple Plot Manager Component +export default function PlotManager() { + const { triggerVariableRefresh } = useVariableContext() + const [plotsConfig, setPlotsConfig] = useState(null) + const [plotVariablesConfig, setPlotVariablesConfig] = useState(null) + const [plotsSchemaData, setPlotsSchemaData] = useState(null) + const [plotVariablesSchemaData, setPlotVariablesSchemaData] = useState(null) + const [selectedPlotId, setSelectedPlotId] = useState('') + const [loading, setLoading] = useState(true) + const toast = useToast() + + const loadPlotData = useCallback(async () => { + setLoading(true) + try { + const [plotsData, plotVariablesData, plotsSchemaResponse, plotVariablesSchemaResponse] = await Promise.all([ + api.readConfig('plot-definitions'), + api.readConfig('plot-variables'), + api.getSchema('plot-definitions'), + api.getSchema('plot-variables') + ]) + + setPlotsConfig(plotsData) + setPlotVariablesConfig(plotVariablesData) + setPlotsSchemaData(plotsSchemaResponse) + setPlotVariablesSchemaData(plotVariablesSchemaResponse) + + // Auto-select first plot if none selected + if (!selectedPlotId && plotsData?.plots?.length > 0) { + setSelectedPlotId(plotsData.plots[0].id) + } + setPlotsSchemaData(plotsSchemaResponse) + } catch (error) { + toast({ + title: '❌ Failed to load plot configurations', + description: error.message, + status: 'error', + duration: 5000 + }) + } finally { + setLoading(false) + } + }, [toast]) + + const savePlotsConfig = async (formData) => { + try { + await api.writeConfig('plot-definitions', formData) + setPlotsConfig(formData) + toast({ + title: '✅ Plot definitions saved', + status: 'success', + duration: 3000 + }) + triggerVariableRefresh() + } catch (error) { + toast({ + title: '❌ Failed to save plot definitions', + description: error.message, + status: 'error', + duration: 5000 + }) + } + } + + const savePlotVariables = async (formData) => { + try { + await api.writeConfig('plot-variables', formData) + setPlotVariablesConfig(formData) + toast({ + title: '✅ Plot variables saved', + status: 'success', + duration: 3000 + }) + } catch (error) { + toast({ + title: '❌ Failed to save plot variables', + description: error.message, + status: 'error', + duration: 5000 + }) + } + } + + // Helper function to get plot definitions from config + const getPlotDefinitions = () => { + return plotsConfig?.plots || [] + } + + // Helper to get dataset IDs for dropdown + const getDatasetIds = () => { + // This would normally come from dataset definitions + // For now, return some placeholder values + return ['DAR', 'Fast', 'Slow'] // TODO: Get from actual dataset definitions + } + + // Helper functions for Type 3 form pattern (Plot Variables) + const getSelectedPlotVariables = () => { + if (!selectedPlotId || !plotVariablesConfig?.variables) return { variables: [] } + + const plotVars = plotVariablesConfig.variables.find( + item => item.plot_id === selectedPlotId + ) + return plotVars || { plot_id: selectedPlotId, variables: [] } + } + + const updateSelectedPlotVariables = (formData) => { + if (!plotVariablesConfig?.variables) { + // Initialize plotVariablesConfig if it doesn't exist + const newConfig = { + variables: [{ plot_id: selectedPlotId, ...formData }] + } + setPlotVariablesConfig(newConfig) + return + } + + const existingIndex = plotVariablesConfig.variables.findIndex( + item => item.plot_id === selectedPlotId + ) + + const updatedVars = [...plotVariablesConfig.variables] + const newVarData = { plot_id: selectedPlotId, ...formData } + + if (existingIndex >= 0) { + updatedVars[existingIndex] = newVarData + } else { + updatedVars.push(newVarData) + } + + setPlotVariablesConfig({ + ...plotVariablesConfig, + variables: updatedVars + }) + } + + useEffect(() => { + loadPlotData() + }, [loadPlotData]) + + if (loading) { + return ( + + + + + Loading plot configurations... + + + + ) + } + + return ( + + + 📈 Plot Manager + + + + + {/* Real-time Charts Section */} + + + 🔴 Real-time Plot Sessions + + Live charts for configured plot sessions - click to expand/collapse + + + + {getPlotDefinitions().length === 0 ? ( + + No plot sessions configured. Create plot definitions below to get started. + + ) : ( + + {getPlotDefinitions().map((plotDef) => ( + {}} + onRemove={() => {}} + /> + ))} + + )} + + + + + + {/* Plot Definitions - Collapsible */} + `${item.name || item.id} (${item.time_window || 60}s)`} + /> + + {/* Plot Variables Configuration - Type 3 Form Pattern */} + + + ⚙️ Plot Variables Configuration + + Select a plot, then configure which variables are displayed in that plot session + + + + {/* Step 1: Plot Selector (Combo) */} + + + + 🎯 Select Plot Session + + + {getPlotDefinitions().length === 0 && ( + + ⚠️ No plots available. Configure plot definitions first above. + + )} + + + {/* Variables Configuration Form */} + {selectedPlotId && ( + + + + ⚙️ Configure Variables for Plot "{selectedPlotId}" + + + {/* Simplified schema for selected plot variables */} + {(() => { + const selectedPlotVars = getSelectedPlotVariables() + + // Schema for this plot's variables - match the real schema + const singlePlotSchema = { + type: "object", + properties: { + variables: { + type: "array", + title: "Variables", + description: `Variables to display in plot ${selectedPlotId}`, + items: { + type: "object", + properties: { + variable_name: { + type: "string", + title: "Variable Name", + description: "Name of the variable from the dataset" + }, + label: { + type: "string", + title: "Display Label", + description: "Label shown in the plot legend" + }, + color: { + type: "string", + title: "Plot Color", + pattern: "^#[0-9A-Fa-f]{6}$", + default: "#3498db" + }, + line_width: { + type: "number", + title: "Line Width", + default: 2, + minimum: 1, + maximum: 10 + }, + y_axis: { + type: "string", + title: "Y-Axis", + enum: ["left", "right"], + default: "left" + }, + enabled: { + type: "boolean", + title: "Show in Plot", + default: true + } + } + } + } + } + } + + // UI Schema for layout + const singlePlotUiSchema = { + "ui:layout": [[ + { "name": "variables", "width": 12 } + ]], + variables: { + items: { + "ui:layout": [[ + { "name": "variable_name", "width": 4 }, + { "name": "label", "width": 3 }, + { "name": "color", "width": 2 }, + { "name": "line_width", "width": 1 }, + { "name": "y_axis", "width": 1 }, + { "name": "enabled", "width": 1 } + ]], + variable_name: { + "ui:widget": "variableSelector", + "ui:placeholder": "Search and select variable from datasets...", + "ui:help": "🔍 Search and select a variable from the configured datasets (includes live values and metadata)" + }, + label: { + "ui:widget": "text", + "ui:placeholder": "Chart legend label...", + "ui:help": "📊 Label shown in the plot legend for this variable" + }, + color: { + "ui:widget": "color", + "ui:help": "🎨 Select the color for this variable in the plot", + "ui:placeholder": "#3498db" + }, + line_width: { + "ui:widget": "updown", + "ui:help": "📏 Thickness of the line in the plot (1-10)", + "ui:options": { "step": 1, "min": 1, "max": 10 } + }, + y_axis: { + "ui:widget": "select", + "ui:help": "📈 Which Y-axis to use for this variable" + }, + enabled: { + "ui:widget": "checkbox", + "ui:help": "✅ Whether to show this variable in the plot" + } + } + } + } + + return ( +
{ + updateSelectedPlotVariables(formData) + savePlotVariables(plotVariablesConfig).then(() => { + // Additional trigger after successful save + triggerVariableRefresh?.() + }) + }} + onChange={({ formData }) => updateSelectedPlotVariables(formData)} + > + + + + +
+ ) + })()} +
+ )} + + {!selectedPlotId && getPlotDefinitions().length > 0 && ( + + + 👆 Select a plot above to configure its variables + + + )} +
+
+
+
+ ) +} diff --git a/frontend/src/pages/Config.jsx b/frontend/src/pages/Config.jsx deleted file mode 100644 index 8d606ca..0000000 --- a/frontend/src/pages/Config.jsx +++ /dev/null @@ -1,167 +0,0 @@ -import React, { useEffect, useMemo, useState } from 'react' -import { Container, Heading, HStack, Button, Menu, MenuButton, MenuList, MenuItem, useColorModeValue, Alert, AlertIcon, Spacer } from '@chakra-ui/react' -import Form from '@rjsf/chakra-ui' -import validator from '@rjsf/validator-ajv8' -import { listSchemas, getSchema, readConfig, writeConfig } from '../services/api.js' -import LayoutObjectFieldTemplate from '../components/rjsf/LayoutObjectFieldTemplate.jsx' -import { widgets } from '../components/rjsf/widgets.jsx' - -function buildUiSchema(schema) { - if (!schema || typeof schema !== 'object') return undefined - - const mapForType = (s) => { - if (!s || typeof s !== 'object') return undefined - const type = s.type - // handle oneOf/anyOf by taking first option for ui mapping - const resolved = type || (Array.isArray(s.oneOf) && s.oneOf[0]?.type) || (Array.isArray(s.anyOf) && s.anyOf[0]?.type) - if (resolved === 'string') return { 'ui:widget': 'text' } - if (resolved === 'integer' || resolved === 'number') return { 'ui:widget': 'updown' } - if (resolved === 'boolean') return { 'ui:widget': 'checkbox' } - if (resolved === 'object' && s.properties) { - const ui = {} - for (const [key, prop] of Object.entries(s.properties)) { - ui[key] = mapForType(prop) - } - return ui - } - if (resolved === 'array' && s.items) { - // Apply mapping to array items when simple types - const itemUi = mapForType(s.items) - return itemUi ? { items: itemUi } : undefined - } - return undefined - } - - return mapForType(schema) -} - -export default function ConfigPage() { - const [schemas, setSchemas] = useState({}) - const [currentId, setCurrentId] = useState('plc') - const [schema, setSchema] = useState(null) - const [uiSchema, setUiSchema] = useState(undefined) - const [formData, setFormData] = useState(null) - const [loading, setLoading] = useState(false) - const [message, setMessage] = useState('') - - const available = useMemo(() => { - if (!schemas) return [] - if (Array.isArray(schemas.schemas)) return schemas.schemas.map(s => s.id) - if (schemas.schemas && typeof schemas.schemas === 'object') return Object.keys(schemas.schemas) - // Fallback: return empty array if no schemas detected - return [] - }, [schemas]) - - const load = async (id) => { - setLoading(true) - setMessage('') - try { - const [schemaResp, dataResp] = await Promise.all([ - getSchema(id), - readConfig(id), - ]) - setSchema(schemaResp.schema) - setUiSchema(schemaResp.ui_schema || buildUiSchema(schemaResp.schema)) - setFormData(dataResp.data) - } catch (e) { - setMessage(e.message || 'Error loading schema/config') - } finally { - setLoading(false) - } - } - - useEffect(() => { - listSchemas().then(setSchemas).catch(() => { }) - }, []) - - useEffect(() => { - if (currentId) { - load(currentId) - } - }, [currentId]) - - const handleSave = async ({ formData: newData }) => { - setLoading(true) - setMessage('') - try { - await writeConfig(currentId, newData) - setFormData(newData) - setMessage('Saved successfully') - } catch (e) { - setMessage(e.message || 'Error saving configuration') - } finally { - setLoading(false) - } - } - - const handleFileImport = async (evt) => { - const file = evt.target.files?.[0] - if (!file) return - try { - const text = await file.text() - const json = JSON.parse(text) - setFormData(json) - setMessage(`Imported ${file.name}`) - } catch (e) { - setMessage('Invalid JSON file') - } - } - - const handleExport = () => { - const blob = new Blob([JSON.stringify(formData ?? {}, null, 2)], { type: 'application/json' }) - const url = URL.createObjectURL(blob) - const a = document.createElement('a') - a.href = url - a.download = `${currentId}_config.json` - a.click() - URL.revokeObjectURL(url) - } - - return ( - - - Config Editor - - Schema: {currentId} - - {available.map(id => ( - setCurrentId(id)}>{id} - ))} - - - - - - - - - - {message && ( - {message} - )} - - {schema && ( -
setFormData(formData)} - uiSchema={uiSchema} - templates={{ ObjectFieldTemplate: LayoutObjectFieldTemplate }} - widgets={widgets} - > - - - - -
- )} -
- ) -} - - diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx index f829df2..5ee7c2f 100644 --- a/frontend/src/pages/Dashboard.jsx +++ b/frontend/src/pages/Dashboard.jsx @@ -1,476 +1,1126 @@ -import React, { useEffect, useMemo, useRef, useState, useCallback } from 'react' -import { Link } from 'react-router-dom' -import { - Box, Container, Flex, Grid, GridItem, HStack, VStack, Heading, Text, Button, Badge, - Table, Thead, Tbody, Tr, Th, Td, Alert, AlertIcon, Card, CardBody, CardHeader, - useColorModeValue, useToast, Tabs, TabList, TabPanels, Tab, TabPanel, - Accordion, AccordionItem, AccordionButton, AccordionPanel, AccordionIcon, - Spacer, IconButton, Divider +import React, { useState, useEffect, useRef, useCallback } from 'react' +import { + Box, + Container, + VStack, + Heading, + Tabs, + TabList, + TabPanels, + Tab, + TabPanel, + Card, + CardBody, + CardHeader, + Button, + Text, + Flex, + Spacer, + HStack, + useColorModeValue, + useToast, + Alert, + AlertIcon, + Spinner, + Badge, + SimpleGrid, + Stat, + StatLabel, + StatNumber, + StatHelpText, + Divider, + Table, + Thead, + Tbody, + Tr, + Th, + Td, + TableContainer, + Code, + Select, + Accordion, + AccordionItem, + AccordionButton, + AccordionPanel, + Collapse, + useDisclosure } from '@chakra-ui/react' +import { ChevronDownIcon, ChevronUpIcon } from '@chakra-ui/icons' import Form from '@rjsf/chakra-ui' import validator from '@rjsf/validator-ajv8' -import LayoutObjectFieldTemplate from '../components/rjsf/LayoutObjectFieldTemplate.jsx' -import { widgets } from '../components/rjsf/widgets.jsx' -import { - getStatus, - getEvents, - listSchemas, - getSchema, - readConfig, - writeConfig, - connectPlc, - disconnectPlc, - startUdpStreaming, - stopUdpStreaming, - activateDataset, - deactivateDataset, -} from '../services/api.js' +import PlotManager from '../components/PlotManagerSimple' +import allWidgets from '../components/widgets/AllWidgets' +import LayoutObjectFieldTemplate from '../components/rjsf/LayoutObjectFieldTemplate' +import { VariableProvider, useVariableContext } from '../contexts/VariableContext' +import * as api from '../services/api' +// Collapsible Array Items Form - Each item in the array is individually collapsible +function CollapsibleArrayItemsForm({ data, schema, uiSchema, onSave, title, icon, getItemLabel }) { + const [formData, setFormData] = useState(data) + const [expandedItems, setExpandedItems] = useState(new Set()) + + useEffect(() => { + setFormData(data) + }, [data]) + + if (!schema || !formData) { + return ( + + + + + Loading {title}... + + + + ) + } + + const arrayKey = Object.keys(formData)[0] + const items = formData[arrayKey] || [] + + const toggleItemExpansion = (index) => { + const newExpanded = new Set(expandedItems) + if (newExpanded.has(index)) { + newExpanded.delete(index) + } else { + newExpanded.add(index) + } + setExpandedItems(newExpanded) + } + + const updateItem = (index, newItemData) => { + const newItems = [...items] + newItems[index] = newItemData + const newFormData = { ...formData, [arrayKey]: newItems } + setFormData(newFormData) + } + + const addItem = () => { + const newItem = {} + const newItems = [...items, newItem] + const newFormData = { ...formData, [arrayKey]: newItems } + setFormData(newFormData) + setExpandedItems(new Set([...expandedItems, items.length])) + } + + const removeItem = (index) => { + const newItems = items.filter((_, i) => i !== index) + const newFormData = { ...formData, [arrayKey]: newItems } + setFormData(newFormData) + // Update expanded items indices + const newExpanded = new Set() + expandedItems.forEach(i => { + if (i < index) newExpanded.add(i) + else if (i > index) newExpanded.add(i - 1) + }) + setExpandedItems(newExpanded) + } + + const saveChanges = () => { + onSave(formData) + } + + // Get item schema from the array schema + const itemSchema = schema?.properties?.[arrayKey]?.items || {} + + // Get item UI schema - extract from the nested structure + const arrayUiSchema = uiSchema?.[arrayKey] || {} + const itemUiSchema = arrayUiSchema.items || {} + + return ( + + + + + {icon} {title} + + {items.length} item{items.length !== 1 ? 's' : ''} configured + + + + + + + + + + + {items.length === 0 ? ( + + + No items configured yet + + + + ) : ( + + {items.map((item, index) => { + const isExpanded = expandedItems.has(index) + const itemLabel = getItemLabel ? getItemLabel(item) : (item.name || item.id || `Item ${index + 1}`) + + return ( + + + + + + #{index + 1} + + + + + + + +
updateItem(index, newItemData)} + > +
{/* Prevents form buttons from showing */} +
+
+
+
+ ) + })} +
+ )} +
+
+ ) +} + +// Simple Collapsible Form Component (for backward compatibility) +function CollapsibleArrayForm({ data, schema, uiSchema, onSave, title, icon, getItemLabel }) { + const [isOpen, setIsOpen] = useState(false) + const [formData, setFormData] = useState(data) + + useEffect(() => { + setFormData(data) + }, [data]) + + if (!schema || !formData) { + return ( + + + + + Loading {title}... + + + + ) + } + + const items = formData[Object.keys(formData)[0]] || [] + + return ( + + + + + {icon} {title} + + {items.length} item{items.length !== 1 ? 's' : ''} configured + + + + + + {/* Show summary when collapsed */} + {!isOpen && items.length > 0 && ( + + Quick Overview: + + {items.slice(0, 5).map((item, index) => ( + + {getItemLabel ? getItemLabel(item) : (item.name || item.id || `Item ${index + 1}`)} + + ))} + {items.length > 5 && ( + + +{items.length - 5} more... + + )} + + + )} + + + + +
onSave(formData)} + onChange={({ formData }) => setFormData(formData)} + > + + + + +
+
+
+
+ ) +} + +// StatusBar Component - Real-time PLC status with action buttons function StatusBar({ status, onRefresh }) { const plcConnected = !!status?.plc_connected const streaming = !!status?.streaming const csvRecording = !!status?.csv_recording - const muted = useColorModeValue('gray.600', 'gray.300') + const [actionLoading, setActionLoading] = useState({}) const toast = useToast() + const setLoading = (action, loading) => { + setActionLoading(prev => ({ ...prev, [action]: loading })) + } + const handleConnectPlc = async () => { + setLoading('connect', true) try { - const result = await connectPlc() - if (result.success) { - toast({ title: 'PLC Connected', status: 'success', duration: 3000 }) - onRefresh?.() - } else { - toast({ title: 'Connection Failed', description: result.message, status: 'error', duration: 5000 }) - } + const result = await api.connectPlc() + toast({ + title: '🔗 PLC Connection', + description: result.message || 'Connection initiated', + status: 'info', + duration: 2000 + }) + setTimeout(() => onRefresh?.(), 1000) } catch (error) { - toast({ title: 'Connection Error', description: error.message, status: 'error', duration: 5000 }) + toast({ + title: '❌ Failed to connect PLC', + description: error.message, + status: 'error', + duration: 3000 + }) + } finally { + setLoading('connect', false) } } const handleDisconnectPlc = async () => { + setLoading('disconnect', true) try { - const result = await disconnectPlc() - if (result.success) { - toast({ title: 'PLC Disconnected', status: 'info', duration: 3000 }) - onRefresh?.() - } + const result = await api.disconnectPlc() + toast({ + title: '❌ PLC Disconnection', + description: result.message || 'Disconnection initiated', + status: 'info', + duration: 2000 + }) + setTimeout(() => onRefresh?.(), 1000) } catch (error) { - toast({ title: 'Disconnect Error', description: error.message, status: 'error', duration: 5000 }) + toast({ + title: '❌ Failed to disconnect PLC', + description: error.message, + status: 'error', + duration: 3000 + }) + } finally { + setLoading('disconnect', false) } } const handleStartStreaming = async () => { + setLoading('startStream', true) try { - const result = await startUdpStreaming() - if (result.success) { - toast({ title: 'UDP Streaming Started', status: 'success', duration: 3000 }) - onRefresh?.() - } + const result = await api.startUdpStreaming() + toast({ + title: '📡 UDP Streaming started', + description: result.message || 'Streaming initiated', + status: 'success', + duration: 2000 + }) + setTimeout(() => onRefresh?.(), 1000) } catch (error) { - toast({ title: 'Streaming Error', description: error.message, status: 'error', duration: 5000 }) + toast({ + title: '❌ Failed to start streaming', + description: error.message, + status: 'error', + duration: 3000 + }) + } finally { + setLoading('startStream', false) } } const handleStopStreaming = async () => { + setLoading('stopStream', true) try { - const result = await stopUdpStreaming() - if (result.success) { - toast({ title: 'UDP Streaming Stopped', status: 'info', duration: 3000 }) - onRefresh?.() - } + const result = await api.stopUdpStreaming() + toast({ + title: '⏹️ UDP Streaming stopped', + description: result.message || 'Streaming stopped', + status: 'info', + duration: 2000 + }) + setTimeout(() => onRefresh?.(), 1000) } catch (error) { - toast({ title: 'Stop Error', description: error.message, status: 'error', duration: 5000 }) + toast({ + title: '❌ Failed to stop streaming', + description: error.message, + status: 'error', + duration: 3000 + }) + } finally { + setLoading('stopStream', false) } } return ( - - - - 🔌 PLC: {plcConnected ? 'Connected' : 'Disconnected'} - {status?.plc_reconnection?.enabled && ( - - 🔄 Auto-reconnection: {status?.plc_reconnection?.active ? 'reconnecting…' : 'enabled'} - - )} - - {plcConnected ? ( - - ) : ( - - )} - + + + + + 🔌 PLC Connection + + {plcConnected ? 'Connected' : 'Disconnected'} + + {status?.plc_reconnection?.enabled && ( + + 🔄 Auto-reconnection: {status?.plc_reconnection?.active ? 'reconnecting…' : 'enabled'} + + )} + + {plcConnected ? ( + + ) : ( + + )} + + + + - - - 📡 UDP Streaming: {streaming ? 'Active' : 'Inactive'} - - {streaming ? ( - - ) : ( - - )} - + + + + 📡 UDP Streaming + + {streaming ? 'Active' : 'Inactive'} + + + {streaming ? ( + + ) : ( + + )} + + + + - - 💾 CSV: {csvRecording ? 'Recording' : 'Inactive'} - {status?.disk_space_info && ( - - 💽 {status.disk_space_info.free_space} free · ⏱️ ~{status.disk_space_info.recording_time_left} - - )} - - + + + + 💾 CSV Recording + + {csvRecording ? 'Recording' : 'Inactive'} + + {status?.disk_space_info && ( + + 💽 {status.disk_space_info.free_space} free
+ ⏱️ ~{status.disk_space_info.recording_time_left} +
+ )} +
+
+
+ ) } -function buildUiSchema(schema) { - if (!schema || typeof schema !== 'object') return undefined - const mapForType = (s) => { - if (!s || typeof s !== 'object') return undefined - const type = s.type - const resolved = type || (Array.isArray(s.oneOf) && s.oneOf[0]?.type) || (Array.isArray(s.anyOf) && s.anyOf[0]?.type) - if (resolved === 'string') return { 'ui:widget': 'text' } - if (resolved === 'integer' || resolved === 'number') return { 'ui:widget': 'updown' } - if (resolved === 'boolean') return { 'ui:widget': 'checkbox' } - if (resolved === 'object' && s.properties) { - const ui = {} - for (const [key, prop] of Object.entries(s.properties)) { - ui[key] = mapForType(prop) - } - return ui - } - if (resolved === 'array' && s.items) { - const itemUi = mapForType(s.items) - return itemUi ? { items: itemUi } : undefined - } - return undefined +// PLC Configuration Panel - Fixed to PLC & UDP settings only +function ConfigurationPanel({ schemaData, formData, onFormChange, onSave, saving, message }) { + const cardBg = useColorModeValue('white', 'gray.700') + const borderColor = useColorModeValue('gray.200', 'gray.600') + + if (!schemaData?.schema || !formData) { + return ( + + + Loading PLC configuration... + + + ) } - return mapForType(schema) + + return ( + + + + 🔧 PLC & UDP Configuration + + Configure PLC connection settings and UDP streaming parameters + + + {message && ( + + + {message} + + )} + + +
onFormChange(formData)} + onSubmit={({ formData }) => onSave(formData)} + > + + + + +
+
+
+ ) } -export default function DashboardPage() { - const [status, setStatus] = useState(null) - const [statusError, setStatusError] = useState('') - const sseRef = useRef(null) - const muted = useColorModeValue('gray.600', 'gray.300') +// Dataset Manager - Type 3 Form Pattern implementation +function DatasetManager() { + const { triggerVariableRefresh } = useVariableContext() + const [datasetsConfig, setDatasetsConfig] = useState(null) + const [variablesConfig, setVariablesConfig] = useState(null) + const [datasetsSchemaData, setDatasetsSchemaData] = useState(null) + const [variablesSchemaData, setVariablesSchemaData] = useState(null) + const [selectedDatasetId, setSelectedDatasetId] = useState('') + const [loading, setLoading] = useState(true) + const toast = useToast() - const [schemas, setSchemas] = useState({}) - const available = useMemo(() => { - if (!schemas) return [] - // Accept multiple shapes from API - const ids = Array.isArray(schemas.schemas) - ? schemas.schemas.map(s => s.id || s) - : (schemas.schemas && typeof schemas.schemas === 'object') - ? Object.keys(schemas.schemas) - : [] - // Ensure PLC config appears before dataset sections on Dashboard - const preferredOrder = ['plc', 'dataset-definitions', 'dataset-variables'] - const orderIndex = (id) => { - const idx = preferredOrder.indexOf(id) - return idx === -1 ? Number.MAX_SAFE_INTEGER : idx + const loadDatasetData = async () => { + try { + setLoading(true) + const [datasetsData, variablesData, datasetsSchemaResponse, variablesSchemaResponse] = await Promise.all([ + api.readConfig('dataset-definitions'), + api.readConfig('dataset-variables'), + api.getSchema('dataset-definitions'), + api.getSchema('dataset-variables') + ]) + + setDatasetsConfig(datasetsData) + setVariablesConfig(variablesData) + setDatasetsSchemaData(datasetsSchemaResponse) + setVariablesSchemaData(variablesSchemaResponse) + + // Auto-select first dataset if none selected + if (!selectedDatasetId && datasetsData?.datasets?.length > 0) { + setSelectedDatasetId(datasetsData.datasets[0].id) + } + } catch (error) { + toast({ + title: '❌ Failed to load dataset data', + description: error.message, + status: 'error', + duration: 3000 + }) + } finally { + setLoading(false) } - return [...ids].sort((a, b) => { - const ai = orderIndex(a) - const bi = orderIndex(b) - if (ai !== bi) return ai - bi - // Keep stable order among non-preferred ids - return 0 - }) - }, [schemas]) + } - const [currentSchemaId, setCurrentSchemaId] = useState('plc') - const [schema, setSchema] = useState(null) - const [uiSchema, setUiSchema] = useState(undefined) + const saveDatasets = async (formData) => { + try { + await api.writeConfig('dataset-definitions', formData) + toast({ + title: '✅ Dataset definitions saved', + status: 'success', + duration: 2000 + }) + setDatasetsConfig(formData) + } catch (error) { + toast({ + title: '❌ Failed to save datasets', + description: error.message, + status: 'error', + duration: 3000 + }) + } + } + + const saveVariables = async (formData) => { + try { + await api.writeConfig('dataset-variables', formData) + toast({ + title: '✅ Dataset variables saved', + status: 'success', + duration: 2000 + }) + setVariablesConfig(formData) + // Trigger refresh of all variable selector widgets + triggerVariableRefresh() + } catch (error) { + toast({ + title: '❌ Failed to save variables', + description: error.message, + status: 'error', + duration: 3000 + }) + } + } + + // Get filtered variables for selected dataset + const getSelectedDatasetVariables = () => { + if (!variablesConfig?.variables || !selectedDatasetId) { + return { variables: [] } + } + + const datasetVars = variablesConfig.variables.find(v => v.dataset_id === selectedDatasetId) + return datasetVars || { variables: [] } + } + + // Update variables for selected dataset + const updateSelectedDatasetVariables = (newVariableData) => { + if (!variablesConfig?.variables || !selectedDatasetId) return + + const updatedVariables = variablesConfig.variables.map(v => + v.dataset_id === selectedDatasetId + ? { ...v, ...newVariableData } + : v + ) + + // If dataset not found, add new entry + if (!variablesConfig.variables.find(v => v.dataset_id === selectedDatasetId)) { + updatedVariables.push({ + dataset_id: selectedDatasetId, + ...newVariableData + }) + } + + const updatedConfig = { ...variablesConfig, variables: updatedVariables } + setVariablesConfig(updatedConfig) + } + + // Available datasets for combo selector + const availableDatasets = datasetsConfig?.datasets || [] + + useEffect(() => { + loadDatasetData() + }, []) + + if (loading) { + return ( + + + + + Loading dataset configurations... + + + + ) + } + + return ( + + + 📊 Dataset Manager + + + + + {/* Dataset Definitions */} + `${item.name || item.id} (${item.id})`} + /> + + {/* Dataset Variables Configuration */} + + + ⚙️ Dataset Variables Configuration + + Select a dataset, then configure its PLC variables and streaming settings + + + + {/* Step 1: Dataset Selector (Combo) */} + + + + 🎯 Select Dataset + + + {availableDatasets.length === 0 && ( + + ⚠️ No datasets available. Configure datasets first above. + + )} + + + {/* Variables Configuration Form */} + {selectedDatasetId && ( + + + + ⚙️ Configure Variables for Dataset "{selectedDatasetId}" + + + {/* Simplified schema for selected dataset variables */} + {(() => { + const selectedDatasetVars = getSelectedDatasetVariables() + + // Schema for this dataset's variables + const singleDatasetSchema = { + type: "object", + properties: { + variables: { + type: "array", + title: "Variables", + description: `PLC variables to record in dataset ${selectedDatasetId}`, + items: { + type: "object", + properties: { + name: { type: "string", title: "Variable Name" }, + area: { + type: "string", + title: "Memory Area", + enum: ["db", "mw", "m", "pew", "pe", "paw", "pa", "e", "a", "mb"], + default: "db" + }, + db: { type: "integer", title: "DB Number", minimum: 1, maximum: 9999 }, + offset: { type: "integer", title: "Offset", minimum: 0, maximum: 8191 }, + bit: { type: "integer", title: "Bit Position", minimum: 0, maximum: 7 }, + type: { + type: "string", + title: "Data Type", + enum: ["real", "int", "dint", "bool", "word", "byte"], + default: "real" + }, + streaming: { type: "boolean", title: "Stream to UDP", default: false } + }, + required: ["name", "area", "offset", "type"] + } + } + } + } + + const singleDatasetUiSchema = { + variables: { + items: { + "ui:layout": [[ + { "name": "name", "width": 3 }, + { "name": "area", "width": 2 }, + { "name": "db", "width": 1 }, + { "name": "offset", "width": 2 }, + { "name": "type", "width": 2 }, + { "name": "streaming", "width": 2 } + ]] + } + } + } + + return ( +
{ + updateSelectedDatasetVariables(formData) + saveVariables(variablesConfig).then(() => { + // Additional trigger after successful save + triggerVariableRefresh() + }) + }} + onChange={({ formData }) => updateSelectedDatasetVariables(formData)} + > + + + + +
+ ) + })()} +
+ )} + + {!selectedDatasetId && availableDatasets.length > 0 && ( + + + 👆 Select a dataset above to configure its variables + + + )} +
+
+
+
+ ) +} + +// Events Display Component +function EventsDisplay({ events, loading, onRefresh }) { + const cardBg = useColorModeValue('white', 'gray.700') + + if (loading) { + return ( + + + + + Loading events... + + + + ) + } + + return ( + + + + 📋 Recent Events + + + + + + + + + + + + + + + + {events?.map((event, index) => ( + + + + + + ))} + +
TimeTypeMessage
+ + {new Date(event.timestamp).toLocaleString()} + + + + {event.level} + + {event.message}
+
+ {(!events || events.length === 0) && ( + + No events found + + )} +
+
+ ) +} + +// Main Dashboard Component - PLC S7-31x Streamer & Logger +export default function Dashboard() { + return ( + + + + ) +} + +// Dashboard Content Component (separated to use context) +function DashboardContent() { + const [status, setStatus] = useState(null) + const [statusLoading, setStatusLoading] = useState(true) + const [statusError, setStatusError] = useState('') + + const [schemaData, setSchemaData] = useState(null) const [formData, setFormData] = useState(null) const [saving, setSaving] = useState(false) const [message, setMessage] = useState('') - const [accordionOpen, setAccordionOpen] = useState(true) - + const [events, setEvents] = useState([]) const [eventsLoading, setEventsLoading] = useState(false) - const loadStatusOnce = async () => { + // Load status once + const loadStatus = useCallback(async () => { try { - const data = await getStatus() - setStatus(data) - } catch (e) { - setStatusError(e.message || 'Error fetching status') + setStatusLoading(true) + setStatusError('') + const statusData = await api.getStatus() + setStatus(statusData) + } catch (error) { + setStatusError(error.message) + } finally { + setStatusLoading(false) } - } + }, []) - const subscribeSSE = () => { - try { - const es = new EventSource('/api/stream/status?interval=2.0') - sseRef.current = es - es.onmessage = (evt) => { - try { - const payload = JSON.parse(evt.data) - if (payload?.type === 'status' && payload.status) { - setStatus(payload.status) - } - } catch { /* ignore */ } + // Real-time status updates via polling + const subscribeSSE = useCallback(() => { + // Use polling for real-time updates (every 5 seconds) + const interval = setInterval(async () => { + try { + const statusData = await api.getStatus() + setStatus(statusData) + setStatusError('') + } catch (error) { + console.error('Status polling error:', error) } - es.onerror = () => { - es.close() - sseRef.current = null - } - } catch { /* ignore */ } - } + }, 5000) - const loadSchemas = async () => { - try { - const data = await listSchemas() - setSchemas(data) - } catch { /* ignore */ } - } + return () => { + clearInterval(interval) + } + }, []) - const loadConfig = async (id) => { + // Load PLC config + const loadConfig = useCallback(async () => { try { - const [schemaResp, dataResp] = await Promise.all([ - getSchema(id), - readConfig(id), + const [schemaResponse, configData] = await Promise.all([ + api.getSchema('plc'), + api.readConfig('plc') ]) - setSchema(schemaResp.schema) - setUiSchema(schemaResp.ui_schema || buildUiSchema(schemaResp.schema)) - setFormData(dataResp.data) - } catch (e) { - setMessage(e.message || 'Error loading config') + setSchemaData(schemaResponse) + setFormData(configData) + setMessage('') + } catch (error) { + console.error('Failed to load PLC config:', error) } - } + }, []) - const saveConfig = async () => { - if (!currentSchemaId) return - setSaving(true) - setMessage('') + // Save config + const saveConfig = useCallback(async (data) => { try { - await writeConfig(currentSchemaId, formData) - setMessage('Saved successfully') - } catch (e) { - setMessage(e.message || 'Error saving configuration') + setSaving(true) + await api.writeConfig('plc', data) + setMessage(`✅ PLC configuration saved successfully`) + setTimeout(() => setMessage(''), 3000) + setFormData(data) + } catch (error) { + setMessage(`❌ Failed to save: ${error.message}`) } finally { setSaving(false) } - } + }, []) - const loadEvents = async () => { - setEventsLoading(true) + // Load events + const loadEvents = useCallback(async () => { try { - const data = await getEvents(5) - if (data?.success) setEvents(data.events || []) + setEventsLoading(true) + const eventsData = await api.getEvents(50) + setEvents(eventsData.events || []) + } catch (error) { + console.error('Failed to load events:', error) } finally { setEventsLoading(false) } - } - - useEffect(() => { - loadStatusOnce() - subscribeSSE() - loadSchemas() - loadEvents() - return () => { if (sseRef.current) sseRef.current.close() } }, []) + // Effects useEffect(() => { - if (currentSchemaId) loadConfig(currentSchemaId) - }, [currentSchemaId]) + loadStatus() + loadConfig() + loadEvents() + + const cleanup = subscribeSSE() + return cleanup + }, [loadStatus, loadConfig, loadEvents, subscribeSSE]) + + if (statusLoading) { + return ( + + + + Loading dashboard... + + + ) + } return ( - - - PLC S7-31x Streamer & Logger - Unified dashboard: status, config and events - - - {statusError && {statusError}} - {status && } - - {/* Sección PLC Config */} - {available.includes('plc') && ( - - )} - - {/* Sección Datasets con Tablas */} - {(available.includes('dataset-definitions') || available.includes('dataset-variables')) && ( - - - - 📊 Dataset Management - - - - - )} - - {/* Sección Plots con Tablas */} - {(available.includes('plot-definitions') || available.includes('plot-variables')) && ( - - - - 📈 Plot Management - - - - 🔴 Real-time plots - - - - - )} - - {/* Otras secciones que no son datasets ni plots */} - {available.filter(id => !['dataset-definitions', 'dataset-variables', 'plot-definitions', 'plot-variables', 'plc'].includes(id)).map((sectionId) => ( - - - - 🧩 {sectionId} - - - - - - - - ))} - - - 📋 Recent Events - - - - - + - - - - - - - - - - - {events.map((ev, idx) => ( - - - - - - ))} - {events.length === 0 && ( - - )} - -
TimeLevelMessage
{ev.timestamp || '-'}{ev.level || ev.type || 'INFO'} - {ev.message || ev.event || '-'} - {ev.details && ( - - {typeof ev.details === 'object' ? JSON.stringify(ev.details) : String(ev.details)} - - )} -
No events
-
+ {statusError && ( + + + Failed to load status: {statusError} + + )} + + + + + + 🔧 Configuration + 📊 Datasets + 📈 Plotting + 📋 Events + + + + + {/* PLC Configuration Section */} + + + + + {/* Dataset Management Section */} + + + + + {/* Plot Management Section */} + + + + + {/* Events Section */} + + + + +
) } - -function SectionControls({ sectionId }) { - const [busy, setBusy] = useState(false) - const [localData, setLocalData] = useState(null) - useEffect(() => { - ; (async () => { - try { - const res = await readConfig(sectionId) - setLocalData(res.data) - } catch { /* ignore */ } - })() - }, [sectionId]) - return ( - - - - - - ) -} - -function SectionForm({ sectionId }) { - const [localSchema, setLocalSchema] = useState(null) - const [localUi, setLocalUi] = useState(null) - const [localData, setLocalData] = useState(null) - const [loading, setLoading] = useState(true) - const [saving, setSaving] = useState(false) - - useEffect(() => { - let mounted = true - ; (async () => { - setLoading(true) - try { - const [schemaResp, dataResp] = await Promise.all([ - getSchema(sectionId), - readConfig(sectionId), - ]) - if (!mounted) return - setLocalSchema(schemaResp.schema) - setLocalUi(schemaResp.ui_schema || buildUiSchema(schemaResp.schema)) - setLocalData(dataResp.data) - } finally { - if (mounted) setLoading(false) - } - })() - return () => { mounted = false } - }, [sectionId]) - - if (loading || !localSchema) return Loading {sectionId}… - - return ( -
setLocalData(formData)} - onSubmit={async ({ formData }) => { - setSaving(true) - try { await writeConfig(sectionId, formData) } finally { setSaving(false) } - }} - uiSchema={localUi} - templates={{ ObjectFieldTemplate: LayoutObjectFieldTemplate }} - widgets={widgets} - > -
- - ) -} - - diff --git a/frontend/src/pages/DashboardNew.jsx b/frontend/src/pages/DashboardNew.jsx index 271849e..5c59bcf 100644 --- a/frontend/src/pages/DashboardNew.jsx +++ b/frontend/src/pages/DashboardNew.jsx @@ -4,11 +4,6 @@ import { Container, VStack, Heading, - Tabs, - TabList, - TabPanels, - Tab, - TabPanel, Card, CardBody, CardHeader, @@ -853,43 +848,28 @@ function DashboardContent() { - - - 🔧 Configuration - 📊 Datasets - 📈 Plotting - 📋 Events - + {/* PLC Configuration Section */} + - - - - + {/* Dataset Management Section */} + - - - + {/* Plot Management Section */} + - - - - - - - - - + {/* Events Section */} + ) diff --git a/frontend/src/pages/Events.jsx b/frontend/src/pages/Events.jsx deleted file mode 100644 index 188ad09..0000000 --- a/frontend/src/pages/Events.jsx +++ /dev/null @@ -1,77 +0,0 @@ -import React, { useEffect, useState } from 'react' -import { getEvents } from '../services/api.js' -import { Container, Heading, HStack, Button, Alert, AlertIcon, Table, Thead, Tbody, Tr, Th, Td, Box, Text, useColorModeValue } from '@chakra-ui/react' - -export default function EventsPage() { - const [events, setEvents] = useState([]) - const [loading, setLoading] = useState(true) - const [error, setError] = useState('') - - const load = async () => { - setLoading(true) - setError('') - try { - const data = await getEvents(100) - if (data && data.success) { - setEvents(data.events || []) - } else { - setError(data?.error || 'Unexpected response') - } - } catch (e) { - setError(e.message || 'Error fetching events') - } finally { - setLoading(false) - } - } - - useEffect(() => { - load() - }, []) - - return ( - - Events - - - - - - {error && {error}} - {loading && !error && Cargando eventos...} - - {!loading && !error && ( - - - - - - - - - - - {events.map((ev, idx) => ( - - - - - - ))} - -
TimeLevelMessage
{ev.timestamp || '-'}{ev.level || ev.type || 'INFO'} - {ev.message || ev.event || '-'} - {ev.details && ( - - {typeof ev.details === 'object' ? JSON.stringify(ev.details) : String(ev.details)} - - )} -
-
- )} -
- ) -} - - diff --git a/frontend/src/pages/Plots.jsx b/frontend/src/pages/Plots.jsx deleted file mode 100644 index 68cd393..0000000 --- a/frontend/src/pages/Plots.jsx +++ /dev/null @@ -1,1094 +0,0 @@ -import React, { useState, useEffect, useRef, useMemo } from 'react' -import { - Box, - Container, - VStack, - HStack, - Text, - Button, - Card, - CardBody, - CardHeader, - Heading, - useColorModeValue, - useToast, - Flex, - Spacer, - Alert, - AlertIcon, - Tabs, - TabList, - TabPanels, - Tab, - TabPanel, - Modal, - ModalOverlay, - ModalContent, - ModalHeader, - ModalBody, - ModalFooter, - ModalCloseButton, - FormControl, - FormLabel, - Input, - NumberInput, - NumberInputField, - NumberInputStepper, - NumberIncrementStepper, - NumberDecrementStepper, - Select, - Checkbox, - Grid, - GridItem, - Badge, - IconButton, - Divider, - useDisclosure -} from '@chakra-ui/react' -import { AddIcon, DeleteIcon, EditIcon, SettingsIcon } from '@chakra-ui/icons' -import ChartjsPlot from '../components/ChartjsPlot' - -// Plot Manager Hook -function usePlotManager() { - const [sessions, setSessions] = useState(new Map()) - const [selectedVariables, setSelectedVariables] = useState(new Map()) - const [availableDatasets, setAvailableDatasets] = useState([]) - const [refreshRates] = useState(new Map()) - const statusUpdateInterval = useRef(null) - const toast = useToast() - - // Chart.js colors for variables - const colors = [ - '#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF', - '#FF9F40', '#8A2BE2', '#FF1493', '#00CED1', '#32CD32', - '#FFB347', '#DA70D6', '#40E0D0', '#EE82EE', '#90EE90' - ] - - useEffect(() => { - checkBackendHealth().then(() => { - loadExistingSessions() - startAutoUpdate() - }) - - return () => { - stopAutoUpdate() - } - }, []) - - const checkBackendHealth = async () => { - try { - console.log('Checking backend health...') - const response = await fetch('/api/health') - const health = await response.json() - console.log('Backend health:', health) - - if (health.plot_manager !== 'available') { - console.warn('Plot manager not available:', health) - toast({ - title: 'Warning', - description: 'Plot manager not available. Some features may not work.', - status: 'warning', - duration: 5000, - isClosable: true - }) - } - } catch (error) { - console.error('Backend health check failed:', error) - toast({ - title: 'Backend Connection Error', - description: 'Could not connect to backend server', - status: 'error', - duration: 5000, - isClosable: true - }) - } - } - - const loadExistingSessions = async () => { - try { - console.log('Fetching plot sessions from /api/plots...') - const response = await fetch('/api/plots') - - if (!response.ok) { - // Try to get the error response as text first - let errorText = '' - try { - errorText = await response.text() - console.error('API Error Response:', errorText) - } catch (textError) { - console.error('Could not read error response:', textError) - } - - throw new Error(`HTTP ${response.status}: ${errorText || response.statusText}`) - } - - const data = await response.json() - console.log('Plot sessions data received:', data) - - if (data.sessions) { - const newSessions = new Map() - for (const session of data.sessions) { - try { - const configResponse = await fetch(`/api/plots/${session.session_id}/config`) - if (!configResponse.ok) { - console.warn(`Failed to load config for session ${session.session_id}: ${configResponse.status}`) - newSessions.set(session.session_id, session) - continue - } - - const configData = await configResponse.json() - - let sessionInfo = session - if (configData.success && configData.config) { - sessionInfo = { - ...session, - is_active: configData.config.is_active, - is_paused: configData.config.is_paused, - variables_count: configData.config.variables ? Object.keys(configData.config.variables).length : session.variables_count, - trigger_enabled: configData.config.trigger_enabled, - trigger_variable: configData.config.trigger_variable, - trigger_on_true: configData.config.trigger_on_true, - config: configData.config - } - } - - newSessions.set(session.session_id, sessionInfo) - } catch (configError) { - console.error(`Error loading config for session ${session.session_id}:`, configError) - newSessions.set(session.session_id, session) - } - } - setSessions(newSessions) - console.log(`Loaded ${newSessions.size} plot sessions`) - } else { - console.log('No plot sessions found in response') - setSessions(new Map()) - } - } catch (error) { - console.error('Error loading existing plot sessions:', error) - toast({ - title: 'Error loading plot sessions', - description: error.message, - status: 'error', - duration: 5000, - isClosable: true - }) - setSessions(new Map()) - } - } - - const startAutoUpdate = () => { - statusUpdateInterval.current = setInterval(() => { - updateAllSessionsStatus() - }, 5000) - } - - const stopAutoUpdate = () => { - if (statusUpdateInterval.current) { - clearInterval(statusUpdateInterval.current) - statusUpdateInterval.current = null - } - } - - const updateAllSessionsStatus = async () => { - const sessionIds = Array.from(sessions.keys()) - for (const sessionId of sessionIds) { - await updateSessionStatus(sessionId) - } - } - - const updateSessionStatus = async (sessionId) => { - try { - const response = await fetch(`/api/plots/${sessionId}/config`) - const data = await response.json() - - if (data.success && data.config) { - setSessions(prev => { - const newSessions = new Map(prev) - const existingSession = newSessions.get(sessionId) - if (existingSession) { - newSessions.set(sessionId, { - ...existingSession, - is_active: data.config.is_active, - is_paused: data.config.is_paused, - config: data.config - }) - } - return newSessions - }) - } - } catch (error) { - console.error(`Error updating session status ${sessionId}:`, error) - } - } - - const controlPlot = async (sessionId, action) => { - try { - const response = await fetch(`/api/plots/${sessionId}/control`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ action: action }) - }) - - const result = await response.json() - - if (result.success) { - await updateSessionStatus(sessionId) - toast({ - title: 'Success', - description: result.message, - status: 'success', - duration: 3000, - isClosable: true - }) - } else { - toast({ - title: 'Error', - description: result.error, - status: 'error', - duration: 5000, - isClosable: true - }) - } - } catch (error) { - console.error(`Error controlling plot ${sessionId}:`, error) - toast({ - title: 'Error', - description: 'Error controlling plot session', - status: 'error', - duration: 5000, - isClosable: true - }) - } - } - - const removePlot = async (sessionId) => { - try { - const response = await fetch(`/api/plots/${sessionId}`, { - method: 'DELETE' - }) - - const result = await response.json() - - if (result.success) { - setSessions(prev => { - const newSessions = new Map(prev) - newSessions.delete(sessionId) - return newSessions - }) - toast({ - title: 'Success', - description: 'Plot session removed', - status: 'success', - duration: 3000, - isClosable: true - }) - } else { - toast({ - title: 'Error', - description: result.error, - status: 'error', - duration: 5000, - isClosable: true - }) - } - } catch (error) { - console.error(`Error removing plot ${sessionId}:`, error) - toast({ - title: 'Error', - description: 'Error removing plot session', - status: 'error', - duration: 5000, - isClosable: true - }) - } - } - - const createNewPlot = async (config) => { - try { - const response = await fetch('/api/plots', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(config) - }) - - const result = await response.json() - - if (result.success) { - const configResponse = await fetch(`/api/plots/${result.session_id}/config`) - const configData = await configResponse.json() - - let sessionConfig = { - session_id: result.session_id, - name: config.name, - variables_count: Object.keys(config.variables).length, - trigger_enabled: config.trigger_enabled, - trigger_variable: config.trigger_variable, - trigger_on_true: config.trigger_on_true, - is_active: false, - is_paused: false - } - - if (configData.success && configData.config) { - sessionConfig = { - ...sessionConfig, - is_active: configData.config.is_active, - is_paused: configData.config.is_paused, - config: configData.config - } - } - - setSessions(prev => { - const newSessions = new Map(prev) - newSessions.set(result.session_id, sessionConfig) - return newSessions - }) - - toast({ - title: 'Success', - description: result.message, - status: 'success', - duration: 3000, - isClosable: true - }) - - return result.session_id - } else { - toast({ - title: 'Error', - description: result.error, - status: 'error', - duration: 5000, - isClosable: true - }) - return null - } - } catch (error) { - console.error('Error creating new plot:', error) - toast({ - title: 'Error', - description: 'Error creating plot session', - status: 'error', - duration: 5000, - isClosable: true - }) - return null - } - } - - const loadDatasetsAndVariables = async () => { - try { - const response = await fetch('/api/datasets') - const data = await response.json() - - if (data.success) { - const datasets = Object.entries(data.datasets).map(([id, dataset]) => ({ - id: id, - name: dataset.name, - variables: dataset.variables || {}, - active: data.active_datasets.includes(id) - })) - setAvailableDatasets(datasets) - return datasets - } - } catch (error) { - console.error('Error loading datasets:', error) - toast({ - title: 'Error', - description: 'Error loading datasets', - status: 'error', - duration: 5000, - isClosable: true - }) - } - return [] - } - - const getColor = (variable, index = null) => { - if (index !== null) { - return colors[index % colors.length] - } - const hash = hashCode(variable) - return colors[hash % colors.length] - } - - const hashCode = (str) => { - let hash = 0 - for (let i = 0; i < str.length; i++) { - const char = str.charCodeAt(i) - hash = ((hash << 5) - hash) + char - hash = hash & hash - } - return Math.abs(hash) - } - - return { - sessions, - selectedVariables, - setSelectedVariables, - availableDatasets, - setAvailableDatasets, - loadExistingSessions, - controlPlot, - removePlot, - createNewPlot, - loadDatasetsAndVariables, - getColor, - updateSessionStatus - } -} - -// Main Plots Page Component -export default function PlotsPage() { - const { - sessions, - selectedVariables, - setSelectedVariables, - availableDatasets, - setAvailableDatasets, - controlPlot, - removePlot, - createNewPlot, - loadDatasetsAndVariables, - getColor, - updateSessionStatus - } = usePlotManager() - - const [selectedSessionId, setSelectedSessionId] = useState(null) - const { isOpen, onOpen, onClose } = useDisclosure() - const [showVariableModal, setShowVariableModal] = useState(false) - const [currentEditingSession, setCurrentEditingSession] = useState(null) - - const cardBg = useColorModeValue('white', 'gray.700') - const borderColor = useColorModeValue('gray.200', 'gray.600') - const muted = useColorModeValue('gray.600', 'gray.300') - - const sessionsList = useMemo(() => Array.from(sessions.values()), [sessions]) - - useEffect(() => { - if (sessionsList.length > 0 && !selectedSessionId) { - setSelectedSessionId(sessionsList[0].session_id) - } - }, [sessionsList, selectedSessionId]) - - const selectedSession = selectedSessionId ? sessions.get(selectedSessionId) : null - - const handleNewPlot = () => { - setCurrentEditingSession(null) - setSelectedVariables(new Map()) - onOpen() - } - - const handleEditPlot = (sessionId) => { - setCurrentEditingSession(sessionId) - const session = sessions.get(sessionId) - if (session && session.config && session.config.variables) { - const newSelectedVariables = new Map() - Object.entries(session.config.variables).forEach(([id, varConfig]) => { - if (varConfig.variable_name) { - newSelectedVariables.set(varConfig.variable_name, { - color: varConfig.color || getColor(varConfig.variable_name), - dataset: 'Unknown' - }) - } - }) - setSelectedVariables(newSelectedVariables) - } - onOpen() - } - - return ( - - - - 📈 Real-Time Plotting - - - - - {sessionsList.length === 0 ? ( - - - No plot sessions created yet. Click "New Plot" to create your first real-time chart. - - ) : ( - s.session_id === selectedSessionId)} onChange={(index) => setSelectedSessionId(sessionsList[index]?.session_id)}> - - {sessionsList.map((session) => ( - - {session.name || `Plot ${session.session_id}`} - - {session.is_active ? (session.is_paused ? 'Paused' : 'Active') : 'Stopped'} - - - ))} - - - - {sessionsList.map((session) => ( - - - - ))} - - - )} - - - - - ) -} - -// Plot Session Panel Component -function PlotSessionPanel({ session, onControl, onRemove, onEdit, onUpdateStatus }) { - const cardBg = useColorModeValue('white', 'gray.700') - const borderColor = useColorModeValue('gray.200', 'gray.600') - const muted = useColorModeValue('gray.600', 'gray.300') - const chartControlsRef = useRef(null) - - const handleRefresh = () => { - onUpdateStatus(session.session_id) - } - - const handleRemove = () => { - if (confirm('¿Estás seguro de que quieres eliminar este plot?')) { - onRemove(session.session_id) - } - } - - const handleControl = async (action) => { - // Handle local chart control first for immediate response - if (chartControlsRef.current) { - switch (action) { - case 'pause': - chartControlsRef.current.pauseStreaming() - break - case 'start': - chartControlsRef.current.resumeStreaming() - break - case 'clear': - chartControlsRef.current.clearChart() - break - case 'stop': - chartControlsRef.current.pauseStreaming() - break - } - } - - // Then handle backend control (no await to avoid UI lag) - onControl(session.session_id, action) - } - - const handleChartReady = (controls) => { - chartControlsRef.current = controls - } - - // Create enhanced session object with chart ready callback - const enhancedSession = { - ...session, - onChartReady: handleChartReady - } - - return ( - - - - - - 📈 {session.name || `Plot ${session.session_id}`} - - Variables: {session.variables_count || 0} | - Status: {session.is_active ? (session.is_paused ? 'Paused' : 'Active') : 'Stopped'} - {session.trigger_enabled && ( - <> | Trigger: {session.trigger_variable} ({session.trigger_on_true ? 'True' : 'False'}) - )} - - - - - - - - - - } - size="sm" - variant="outline" - onClick={() => onEdit(session.session_id)} - aria-label="Edit plot" - /> - } - size="sm" - variant="outline" - onClick={handleRefresh} - aria-label="Refresh status" - /> - } - size="sm" - variant="outline" - colorScheme="red" - onClick={handleRemove} - aria-label="Remove plot" - /> - - - - - - - - - ) -} - -// Plot Form Modal Component -function PlotFormModal({ - isOpen, - onClose, - selectedVariables, - setSelectedVariables, - availableDatasets, - setAvailableDatasets, - onCreatePlot, - loadDatasetsAndVariables, - getColor, - currentEditingSession, - sessions -}) { - const [plotName, setPlotName] = useState('') - const [timeWindow, setTimeWindow] = useState(60) - const [yMin, setYMin] = useState('') - const [yMax, setYMax] = useState('') - const [triggerEnabled, setTriggerEnabled] = useState(false) - const [triggerVariable, setTriggerVariable] = useState('') - const [triggerOnTrue, setTriggerOnTrue] = useState(true) - const [showVariableSelector, setShowVariableSelector] = useState(false) - - const toast = useToast() - - useEffect(() => { - if (isOpen) { - loadDatasetsAndVariables().then(setAvailableDatasets) - - if (currentEditingSession) { - const session = sessions.get(currentEditingSession) - if (session && session.config) { - setPlotName(session.config.name || '') - setTimeWindow(session.config.time_window || 60) - setYMin(session.config.y_min || '') - setYMax(session.config.y_max || '') - setTriggerEnabled(session.config.trigger_enabled || false) - setTriggerVariable(session.config.trigger_variable || '') - setTriggerOnTrue(session.config.trigger_on_true !== false) - } - } else { - // Reset form for new plot - setPlotName('') - setTimeWindow(60) - setYMin('') - setYMax('') - setTriggerEnabled(false) - setTriggerVariable('') - setTriggerOnTrue(true) - setSelectedVariables(new Map()) - } - } - }, [isOpen, currentEditingSession, sessions, loadDatasetsAndVariables, setAvailableDatasets, setSelectedVariables]) - - const handleSubmit = async () => { - if (selectedVariables.size === 0) { - toast({ - title: 'Error', - description: 'Please select at least one variable', - status: 'error', - duration: 3000, - isClosable: true - }) - return - } - - // Convert selectedVariables to new structure with variable_name property - const variablesConfig = {} - let varCounter = 1 - selectedVariables.forEach((info, variableName) => { - variablesConfig[`var_${varCounter}`] = { - variable_name: variableName, - color: info.color, - enabled: true - } - varCounter++ - }) - - const config = { - name: plotName || `Plot ${Date.now()}`, - variables: variablesConfig, - time_window: parseInt(timeWindow) || 60, - y_min: yMin ? parseFloat(yMin) : null, - y_max: yMax ? parseFloat(yMax) : null, - trigger_enabled: triggerEnabled, - trigger_variable: triggerVariable || null, - trigger_on_true: triggerOnTrue - } - - const sessionId = await onCreatePlot(config) - if (sessionId) { - onClose() - } - } - - return ( - <> - - - - - {currentEditingSession ? 'Edit Plot' : 'Create New Plot'} - - - - - - Plot Name - setPlotName(e.target.value)} - placeholder="Temperature Monitoring" - /> - - - - Variables to Plot - - - - {selectedVariables.size} variables selected - - - - - {selectedVariables.size === 0 ? ( - No variables selected - ) : ( - - {Array.from(selectedVariables.entries()).map(([varName, info]) => ( - - - {varName} - - ))} - - )} - - - - - - - Time Window (seconds) - setTimeWindow(val)} min={10} max={3600}> - - - - - - - - - - Y-Axis Range (optional) - - setYMin(e.target.value)} - /> - to - setYMax(e.target.value)} - /> - - - - - - setTriggerEnabled(e.target.checked)} - > - Enable Trigger System - - - - {triggerEnabled && ( - - - Trigger Variable - - - - Trigger Condition - setTriggerOnTrue(e.target.checked)} - > - Trigger on True - - - - )} - - - - - - - - - - setShowVariableSelector(false)} - availableDatasets={availableDatasets} - selectedVariables={selectedVariables} - setSelectedVariables={setSelectedVariables} - getColor={getColor} - /> - - ) -} - -// Variable Selector Modal Component -function VariableSelectorModal({ - isOpen, - onClose, - availableDatasets, - selectedVariables, - setSelectedVariables, - getColor -}) { - const [selectedDatasetId, setSelectedDatasetId] = useState('') - - const selectedDataset = availableDatasets.find(d => d.id === selectedDatasetId) - - const handleVariableToggle = (varName, selected) => { - const newSelectedVariables = new Map(selectedVariables) - - if (selected) { - const color = getColor(varName) - const datasetName = selectedDataset?.name || selectedDatasetId - - newSelectedVariables.set(varName, { - color: color, - dataset: datasetName - }) - } else { - newSelectedVariables.delete(varName) - } - - setSelectedVariables(newSelectedVariables) - } - - const handleColorChange = (varName, color) => { - const newSelectedVariables = new Map(selectedVariables) - const varData = newSelectedVariables.get(varName) - if (varData) { - newSelectedVariables.set(varName, { ...varData, color }) - setSelectedVariables(newSelectedVariables) - } - } - - return ( - - - - 🎨 Select Variables & Colors - - - - - 📊 Datasets - - {availableDatasets.map((dataset) => ( - setSelectedDatasetId(dataset.id)} - bg={selectedDatasetId === dataset.id ? 'blue.50' : 'white'} - borderColor={selectedDatasetId === dataset.id ? 'blue.500' : 'gray.200'} - _hover={{ borderColor: 'blue.300' }} - > - - - - {dataset.name} - - {Object.keys(dataset.variables).length} variables - - - - - {dataset.active ? 'Active' : 'Inactive'} - - - - - ))} - - - - - - 🔧 Variables - {selectedDataset && ( - - - - - )} - - - - {!selectedDataset ? ( - - Select a dataset to see its variables - - ) : ( - - {Object.entries(selectedDataset.variables).map(([varName, varConfig]) => { - const isSelected = selectedVariables.has(varName) - const selectedData = selectedVariables.get(varName) - - return ( - - - - handleVariableToggle(varName, e.target.checked)} - /> - - {varName} - - {varConfig.type?.toUpperCase()} - {varConfig.area?.toUpperCase()}{varConfig.db ? varConfig.db + '.' : ''}{varConfig.offset} - - - {isSelected && ( - handleColorChange(varName, e.target.value)} - style={{ width: '32px', height: '24px', border: 'none', cursor: 'pointer' }} - /> - )} - - - - ) - })} - - )} - - - - - - - - 📝 Selected Variables Summary - {selectedVariables.size === 0 ? ( - No variables selected - ) : ( - - {Array.from(selectedVariables.entries()).map(([varName, info]) => ( - - - {varName} - ({info.dataset}) - - ))} - - )} - - - - - - - - - ) -} diff --git a/frontend/src/pages/PlotsPageNew.jsx b/frontend/src/pages/PlotsPageNew.jsx deleted file mode 100644 index f5ec533..0000000 --- a/frontend/src/pages/PlotsPageNew.jsx +++ /dev/null @@ -1,1013 +0,0 @@ -import React, { useState, useEffect, useRef, useMemo } from 'react' -import { - Box, - Container, - VStack, - HStack, - Text, - Button, - Card, - CardBody, - CardHeader, - Heading, - useColorModeValue, - useToast, - Flex, - Spacer, - Alert, - AlertIcon, - Tabs, - TabList, - TabPanels, - Tab, - TabPanel, - Modal, - ModalOverlay, - ModalContent, - ModalHeader, - ModalBody, - ModalFooter, - ModalCloseButton, - FormControl, - FormLabel, - Input, - NumberInput, - NumberInputField, - NumberInputStepper, - NumberIncrementStepper, - NumberDecrementStepper, - Select, - Checkbox, - Grid, - GridItem, - Badge, - IconButton, - Divider, - useDisclosure -} from '@chakra-ui/react' -import { AddIcon, DeleteIcon, EditIcon, SettingsIcon } from '@chakra-ui/icons' - -// Plot Manager Hook -function usePlotManager() { - const [sessions, setSessions] = useState(new Map()) - const [selectedVariables, setSelectedVariables] = useState(new Map()) - const [availableDatasets, setAvailableDatasets] = useState([]) - const [refreshRates] = useState(new Map()) - const statusUpdateInterval = useRef(null) - const toast = useToast() - - // Chart.js colors for variables - const colors = [ - '#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF', - '#FF9F40', '#8A2BE2', '#FF1493', '#00CED1', '#32CD32', - '#FFB347', '#DA70D6', '#40E0D0', '#EE82EE', '#90EE90' - ] - - useEffect(() => { - loadExistingSessions() - startAutoUpdate() - - return () => { - stopAutoUpdate() - } - }, []) - - const loadExistingSessions = async () => { - try { - const response = await fetch('/api/plots') - const data = await response.json() - - if (data.sessions) { - const newSessions = new Map() - for (const session of data.sessions) { - try { - const configResponse = await fetch(`/api/plots/${session.session_id}/config`) - const configData = await configResponse.json() - - let sessionInfo = session - if (configData.success && configData.config) { - sessionInfo = { - ...session, - is_active: configData.config.is_active, - is_paused: configData.config.is_paused, - variables_count: configData.config.variables ? Object.keys(configData.config.variables).length : session.variables_count, - trigger_enabled: configData.config.trigger_enabled, - trigger_variable: configData.config.trigger_variable, - trigger_on_true: configData.config.trigger_on_true, - config: configData.config - } - } - - newSessions.set(session.session_id, sessionInfo) - } catch (configError) { - console.error(`Error loading config for session ${session.session_id}:`, configError) - newSessions.set(session.session_id, session) - } - } - setSessions(newSessions) - } - } catch (error) { - console.error('Error loading existing plot sessions:', error) - } - } - - const startAutoUpdate = () => { - statusUpdateInterval.current = setInterval(() => { - updateAllSessionsStatus() - }, 5000) - } - - const stopAutoUpdate = () => { - if (statusUpdateInterval.current) { - clearInterval(statusUpdateInterval.current) - statusUpdateInterval.current = null - } - } - - const updateAllSessionsStatus = async () => { - const sessionIds = Array.from(sessions.keys()) - for (const sessionId of sessionIds) { - await updateSessionStatus(sessionId) - } - } - - const updateSessionStatus = async (sessionId) => { - try { - const response = await fetch(`/api/plots/${sessionId}/config`) - const data = await response.json() - - if (data.success && data.config) { - setSessions(prev => { - const newSessions = new Map(prev) - const existingSession = newSessions.get(sessionId) - if (existingSession) { - newSessions.set(sessionId, { - ...existingSession, - is_active: data.config.is_active, - is_paused: data.config.is_paused, - config: data.config - }) - } - return newSessions - }) - } - } catch (error) { - console.error(`Error updating session status ${sessionId}:`, error) - } - } - - const controlPlot = async (sessionId, action) => { - try { - const response = await fetch(`/api/plots/${sessionId}/control`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ action: action }) - }) - - const result = await response.json() - - if (result.success) { - await updateSessionStatus(sessionId) - toast({ - title: 'Success', - description: result.message, - status: 'success', - duration: 3000, - isClosable: true - }) - } else { - toast({ - title: 'Error', - description: result.error, - status: 'error', - duration: 5000, - isClosable: true - }) - } - } catch (error) { - console.error(`Error controlling plot ${sessionId}:`, error) - toast({ - title: 'Error', - description: 'Error controlling plot session', - status: 'error', - duration: 5000, - isClosable: true - }) - } - } - - const removePlot = async (sessionId) => { - try { - const response = await fetch(`/api/plots/${sessionId}`, { - method: 'DELETE' - }) - - const result = await response.json() - - if (result.success) { - setSessions(prev => { - const newSessions = new Map(prev) - newSessions.delete(sessionId) - return newSessions - }) - toast({ - title: 'Success', - description: 'Plot session removed', - status: 'success', - duration: 3000, - isClosable: true - }) - } else { - toast({ - title: 'Error', - description: result.error, - status: 'error', - duration: 5000, - isClosable: true - }) - } - } catch (error) { - console.error(`Error removing plot ${sessionId}:`, error) - toast({ - title: 'Error', - description: 'Error removing plot session', - status: 'error', - duration: 5000, - isClosable: true - }) - } - } - - const createNewPlot = async (config) => { - try { - const response = await fetch('/api/plots', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(config) - }) - - const result = await response.json() - - if (result.success) { - const configResponse = await fetch(`/api/plots/${result.session_id}/config`) - const configData = await configResponse.json() - - let sessionConfig = { - session_id: result.session_id, - name: config.name, - variables_count: Object.keys(config.variables).length, - trigger_enabled: config.trigger_enabled, - trigger_variable: config.trigger_variable, - trigger_on_true: config.trigger_on_true, - is_active: false, - is_paused: false - } - - if (configData.success && configData.config) { - sessionConfig = { - ...sessionConfig, - is_active: configData.config.is_active, - is_paused: configData.config.is_paused, - config: configData.config - } - } - - setSessions(prev => { - const newSessions = new Map(prev) - newSessions.set(result.session_id, sessionConfig) - return newSessions - }) - - toast({ - title: 'Success', - description: result.message, - status: 'success', - duration: 3000, - isClosable: true - }) - - return result.session_id - } else { - toast({ - title: 'Error', - description: result.error, - status: 'error', - duration: 5000, - isClosable: true - }) - return null - } - } catch (error) { - console.error('Error creating new plot:', error) - toast({ - title: 'Error', - description: 'Error creating plot session', - status: 'error', - duration: 5000, - isClosable: true - }) - return null - } - } - - const loadDatasetsAndVariables = async () => { - try { - const response = await fetch('/api/datasets') - const data = await response.json() - - if (data.success) { - const datasets = Object.entries(data.datasets).map(([id, dataset]) => ({ - id: id, - name: dataset.name, - variables: dataset.variables || {}, - active: data.active_datasets.includes(id) - })) - setAvailableDatasets(datasets) - return datasets - } - } catch (error) { - console.error('Error loading datasets:', error) - toast({ - title: 'Error', - description: 'Error loading datasets', - status: 'error', - duration: 5000, - isClosable: true - }) - } - return [] - } - - const getColor = (variable, index = null) => { - if (index !== null) { - return colors[index % colors.length] - } - const hash = hashCode(variable) - return colors[hash % colors.length] - } - - const hashCode = (str) => { - let hash = 0 - for (let i = 0; i < str.length; i++) { - const char = str.charCodeAt(i) - hash = ((hash << 5) - hash) + char - hash = hash & hash - } - return Math.abs(hash) - } - - return { - sessions, - selectedVariables, - setSelectedVariables, - availableDatasets, - setAvailableDatasets, - loadExistingSessions, - controlPlot, - removePlot, - createNewPlot, - loadDatasetsAndVariables, - getColor, - updateSessionStatus - } -} - -// Main Plots Page Component -export default function PlotsPageNew() { - const { - sessions, - selectedVariables, - setSelectedVariables, - availableDatasets, - setAvailableDatasets, - controlPlot, - removePlot, - createNewPlot, - loadDatasetsAndVariables, - getColor, - updateSessionStatus - } = usePlotManager() - - const [selectedSessionId, setSelectedSessionId] = useState(null) - const { isOpen, onOpen, onClose } = useDisclosure() - const [showVariableModal, setShowVariableModal] = useState(false) - const [currentEditingSession, setCurrentEditingSession] = useState(null) - - const cardBg = useColorModeValue('white', 'gray.700') - const borderColor = useColorModeValue('gray.200', 'gray.600') - const muted = useColorModeValue('gray.600', 'gray.300') - - const sessionsList = useMemo(() => Array.from(sessions.values()), [sessions]) - - useEffect(() => { - if (sessionsList.length > 0 && !selectedSessionId) { - setSelectedSessionId(sessionsList[0].session_id) - } - }, [sessionsList, selectedSessionId]) - - const selectedSession = selectedSessionId ? sessions.get(selectedSessionId) : null - - const handleNewPlot = () => { - setCurrentEditingSession(null) - setSelectedVariables(new Map()) - onOpen() - } - - const handleEditPlot = (sessionId) => { - setCurrentEditingSession(sessionId) - const session = sessions.get(sessionId) - if (session && session.config && session.config.variables) { - const newSelectedVariables = new Map() - Object.entries(session.config.variables).forEach(([id, varConfig]) => { - if (varConfig.variable_name) { - newSelectedVariables.set(varConfig.variable_name, { - color: varConfig.color || getColor(varConfig.variable_name), - dataset: 'Unknown' - }) - } - }) - setSelectedVariables(newSelectedVariables) - } - onOpen() - } - - return ( - - - - 📈 Real-Time Plotting - - - - - {sessionsList.length === 0 ? ( - - - No plot sessions created yet. Click "New Plot" to create your first real-time chart. - - ) : ( - s.session_id === selectedSessionId)} onChange={(index) => setSelectedSessionId(sessionsList[index]?.session_id)}> - - {sessionsList.map((session) => ( - - {session.name || `Plot ${session.session_id}`} - - {session.is_active ? (session.is_paused ? 'Paused' : 'Active') : 'Stopped'} - - - ))} - - - - {sessionsList.map((session) => ( - - - - ))} - - - )} - - - - - ) -} - -// Plot Session Panel Component -function PlotSessionPanel({ session, onControl, onRemove, onEdit, onUpdateStatus }) { - const cardBg = useColorModeValue('white', 'gray.700') - const borderColor = useColorModeValue('gray.200', 'gray.600') - const muted = useColorModeValue('gray.600', 'gray.300') - - const handleRefresh = () => { - onUpdateStatus(session.session_id) - } - - const handleRemove = () => { - if (confirm('¿Estás seguro de que quieres eliminar este plot?')) { - onRemove(session.session_id) - } - } - - return ( - - - - - - 📈 {session.name || `Plot ${session.session_id}`} - - Variables: {session.variables_count || 0} | - Status: {session.is_active ? (session.is_paused ? 'Paused' : 'Active') : 'Stopped'} - {session.trigger_enabled && ( - <> | Trigger: {session.trigger_variable} ({session.trigger_on_true ? 'True' : 'False'}) - )} - - - - - - - - - - } - size="sm" - variant="outline" - onClick={() => onEdit(session.session_id)} - aria-label="Edit plot" - /> - } - size="sm" - variant="outline" - onClick={handleRefresh} - aria-label="Refresh status" - /> - } - size="sm" - variant="outline" - colorScheme="red" - onClick={handleRemove} - aria-label="Remove plot" - /> - - - - - - - 📊 Chart.js Plot Area - - Session ID: {session.session_id} - - - This would integrate with Chart.js + chartjs-plugin-streaming - - - - - - - ) -} - -// Plot Form Modal Component -function PlotFormModal({ - isOpen, - onClose, - selectedVariables, - setSelectedVariables, - availableDatasets, - setAvailableDatasets, - onCreatePlot, - loadDatasetsAndVariables, - getColor, - currentEditingSession, - sessions -}) { - const [plotName, setPlotName] = useState('') - const [timeWindow, setTimeWindow] = useState(60) - const [yMin, setYMin] = useState('') - const [yMax, setYMax] = useState('') - const [triggerEnabled, setTriggerEnabled] = useState(false) - const [triggerVariable, setTriggerVariable] = useState('') - const [triggerOnTrue, setTriggerOnTrue] = useState(true) - const [showVariableSelector, setShowVariableSelector] = useState(false) - - const toast = useToast() - - useEffect(() => { - if (isOpen) { - loadDatasetsAndVariables().then(setAvailableDatasets) - - if (currentEditingSession) { - const session = sessions.get(currentEditingSession) - if (session && session.config) { - setPlotName(session.config.name || '') - setTimeWindow(session.config.time_window || 60) - setYMin(session.config.y_min || '') - setYMax(session.config.y_max || '') - setTriggerEnabled(session.config.trigger_enabled || false) - setTriggerVariable(session.config.trigger_variable || '') - setTriggerOnTrue(session.config.trigger_on_true !== false) - } - } else { - // Reset form for new plot - setPlotName('') - setTimeWindow(60) - setYMin('') - setYMax('') - setTriggerEnabled(false) - setTriggerVariable('') - setTriggerOnTrue(true) - setSelectedVariables(new Map()) - } - } - }, [isOpen, currentEditingSession, sessions, loadDatasetsAndVariables, setAvailableDatasets, setSelectedVariables]) - - const handleSubmit = async () => { - if (selectedVariables.size === 0) { - toast({ - title: 'Error', - description: 'Please select at least one variable', - status: 'error', - duration: 3000, - isClosable: true - }) - return - } - - // Convert selectedVariables to new structure with variable_name property - const variablesConfig = {} - let varCounter = 1 - selectedVariables.forEach((info, variableName) => { - variablesConfig[`var_${varCounter}`] = { - variable_name: variableName, - color: info.color, - enabled: true - } - varCounter++ - }) - - const config = { - name: plotName || `Plot ${Date.now()}`, - variables: variablesConfig, - time_window: parseInt(timeWindow) || 60, - y_min: yMin ? parseFloat(yMin) : null, - y_max: yMax ? parseFloat(yMax) : null, - trigger_enabled: triggerEnabled, - trigger_variable: triggerVariable || null, - trigger_on_true: triggerOnTrue - } - - const sessionId = await onCreatePlot(config) - if (sessionId) { - onClose() - } - } - - return ( - <> - - - - - {currentEditingSession ? 'Edit Plot' : 'Create New Plot'} - - - - - - Plot Name - setPlotName(e.target.value)} - placeholder="Temperature Monitoring" - /> - - - - Variables to Plot - - - - {selectedVariables.size} variables selected - - - - - {selectedVariables.size === 0 ? ( - No variables selected - ) : ( - - {Array.from(selectedVariables.entries()).map(([varName, info]) => ( - - - {varName} - - ))} - - )} - - - - - - - Time Window (seconds) - setTimeWindow(val)} min={10} max={3600}> - - - - - - - - - - Y-Axis Range (optional) - - setYMin(e.target.value)} - /> - to - setYMax(e.target.value)} - /> - - - - - - setTriggerEnabled(e.target.checked)} - > - Enable Trigger System - - - - {triggerEnabled && ( - - - Trigger Variable - - - - Trigger Condition - setTriggerOnTrue(e.target.checked)} - > - Trigger on True - - - - )} - - - - - - - - - - setShowVariableSelector(false)} - availableDatasets={availableDatasets} - selectedVariables={selectedVariables} - setSelectedVariables={setSelectedVariables} - getColor={getColor} - /> - - ) -} - -// Variable Selector Modal Component -function VariableSelectorModal({ - isOpen, - onClose, - availableDatasets, - selectedVariables, - setSelectedVariables, - getColor -}) { - const [selectedDatasetId, setSelectedDatasetId] = useState('') - - const selectedDataset = availableDatasets.find(d => d.id === selectedDatasetId) - - const handleVariableToggle = (varName, selected) => { - const newSelectedVariables = new Map(selectedVariables) - - if (selected) { - const color = getColor(varName) - const datasetName = selectedDataset?.name || selectedDatasetId - - newSelectedVariables.set(varName, { - color: color, - dataset: datasetName - }) - } else { - newSelectedVariables.delete(varName) - } - - setSelectedVariables(newSelectedVariables) - } - - const handleColorChange = (varName, color) => { - const newSelectedVariables = new Map(selectedVariables) - const varData = newSelectedVariables.get(varName) - if (varData) { - newSelectedVariables.set(varName, { ...varData, color }) - setSelectedVariables(newSelectedVariables) - } - } - - return ( - - - - 🎨 Select Variables & Colors - - - - - 📊 Datasets - - {availableDatasets.map((dataset) => ( - setSelectedDatasetId(dataset.id)} - bg={selectedDatasetId === dataset.id ? 'blue.50' : 'white'} - borderColor={selectedDatasetId === dataset.id ? 'blue.500' : 'gray.200'} - _hover={{ borderColor: 'blue.300' }} - > - - - - {dataset.name} - - {Object.keys(dataset.variables).length} variables - - - - - {dataset.active ? 'Active' : 'Inactive'} - - - - - ))} - - - - - - 🔧 Variables - {selectedDataset && ( - - - - - )} - - - - {!selectedDataset ? ( - - Select a dataset to see its variables - - ) : ( - - {Object.entries(selectedDataset.variables).map(([varName, varConfig]) => { - const isSelected = selectedVariables.has(varName) - const selectedData = selectedVariables.get(varName) - - return ( - - - - handleVariableToggle(varName, e.target.checked)} - /> - - {varName} - - {varConfig.type?.toUpperCase()} - {varConfig.area?.toUpperCase()}{varConfig.db ? varConfig.db + '.' : ''}{varConfig.offset} - - - {isSelected && ( - handleColorChange(varName, e.target.value)} - style={{ width: '32px', height: '24px', border: 'none', cursor: 'pointer' }} - /> - )} - - - - ) - })} - - )} - - - - - - - - 📝 Selected Variables Summary - {selectedVariables.size === 0 ? ( - No variables selected - ) : ( - - {Array.from(selectedVariables.entries()).map(([varName, info]) => ( - - - {varName} - ({info.dataset}) - - ))} - - )} - - - - - - - - - ) -} diff --git a/main.py b/main.py index 48e0f5b..3ba32bd 100644 --- a/main.py +++ b/main.py @@ -70,20 +70,13 @@ def serve_image(filename): return send_from_directory(".images", filename) -@app.route("/static/") -def serve_static(filename): - """Serve static files (CSS, JS, etc.)""" - return send_from_directory("static", filename) - - @app.route("/favicon.ico") def serve_favicon(): - """Serve application favicon from robust locations. + """Serve application favicon from React public folder. Priority: 1) frontend/public/favicon.ico 2) frontend/public/record.png - 3) static/icons/record.png """ # Use absolute paths for reliability (works in dev and bundled) public_dir = project_path("frontend", "public") @@ -95,28 +88,19 @@ def serve_favicon(): if os.path.exists(public_record): return send_from_directory(public_dir, "record.png") - # Fallback: static/icons - static_icons_dir = project_path("static", "icons") - if os.path.exists(os.path.join(static_icons_dir, "record.png")): - return send_from_directory(static_icons_dir, "record.png") - # Final fallback: 404 return Response("Favicon not found", status=404, mimetype="text/plain") @app.route("/record.png") def serve_public_record_png(): - """Serve /record.png from the React public folder with fallbacks.""" + """Serve /record.png from the React public folder.""" public_dir = project_path("frontend", "public") public_record = os.path.join(public_dir, "record.png") if os.path.exists(public_record): return send_from_directory(public_dir, "record.png") - static_icons_dir = project_path("static", "icons") - if os.path.exists(os.path.join(static_icons_dir, "record.png")): - return send_from_directory(static_icons_dir, "record.png") - return Response("record.png not found", status=404, mimetype="text/plain") @@ -2111,9 +2095,6 @@ def main(): while retry_count < max_retries: try: - # Create templates directory if it doesn't exist - os.makedirs("templates", exist_ok=True) - print("🚀 Starting Flask server for PLC S7-315 Streamer") print("📊 Web interface available at: http://localhost:5050") print("🔧 Configure your PLC and variables through the web interface") diff --git a/static/css/pico.min.css b/static/css/pico.min.css deleted file mode 100644 index e10ec26..0000000 --- a/static/css/pico.min.css +++ /dev/null @@ -1,4 +0,0 @@ -@charset "UTF-8";/*! - * Pico CSS ✨ v2.1.1 (https://picocss.com) - * Copyright 2019-2025 - Licensed under MIT - */:host,:root{--pico-font-family-emoji:"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--pico-font-family-sans-serif:system-ui,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,Helvetica,Arial,"Helvetica Neue",sans-serif,var(--pico-font-family-emoji);--pico-font-family-monospace:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace,var(--pico-font-family-emoji);--pico-font-family:var(--pico-font-family-sans-serif);--pico-line-height:1.5;--pico-font-weight:400;--pico-font-size:100%;--pico-text-underline-offset:0.1rem;--pico-border-radius:0.25rem;--pico-border-width:0.0625rem;--pico-outline-width:0.125rem;--pico-transition:0.2s ease-in-out;--pico-spacing:1rem;--pico-typography-spacing-vertical:1rem;--pico-block-spacing-vertical:var(--pico-spacing);--pico-block-spacing-horizontal:var(--pico-spacing);--pico-grid-column-gap:var(--pico-spacing);--pico-grid-row-gap:var(--pico-spacing);--pico-form-element-spacing-vertical:0.75rem;--pico-form-element-spacing-horizontal:1rem;--pico-group-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-group-box-shadow-focus-with-button:0 0 0 var(--pico-outline-width) var(--pico-primary-focus);--pico-group-box-shadow-focus-with-input:0 0 0 0.0625rem var(--pico-form-element-border-color);--pico-modal-overlay-backdrop-filter:blur(0.375rem);--pico-nav-element-spacing-vertical:1rem;--pico-nav-element-spacing-horizontal:0.5rem;--pico-nav-link-spacing-vertical:0.5rem;--pico-nav-link-spacing-horizontal:0.5rem;--pico-nav-breadcrumb-divider:">";--pico-icon-checkbox:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");--pico-icon-minus:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E");--pico-icon-chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--pico-icon-date:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E");--pico-icon-time:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E");--pico-icon-search:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E");--pico-icon-close:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E");--pico-icon-loading:url("data:image/svg+xml,%3Csvg fill='none' height='24' width='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' %3E%3Cstyle%3E g %7B animation: rotate 2s linear infinite; transform-origin: center center; %7D circle %7B stroke-dasharray: 75,100; stroke-dashoffset: -5; animation: dash 1.5s ease-in-out infinite; stroke-linecap: round; %7D @keyframes rotate %7B 0%25 %7B transform: rotate(0deg); %7D 100%25 %7B transform: rotate(360deg); %7D %7D @keyframes dash %7B 0%25 %7B stroke-dasharray: 1,100; stroke-dashoffset: 0; %7D 50%25 %7B stroke-dasharray: 44.5,100; stroke-dashoffset: -17.5; %7D 100%25 %7B stroke-dasharray: 44.5,100; stroke-dashoffset: -62; %7D %7D %3C/style%3E%3Cg%3E%3Ccircle cx='12' cy='12' r='10' fill='none' stroke='rgb(136, 145, 164)' stroke-width='4' /%3E%3C/g%3E%3C/svg%3E")}@media (min-width:576px){:host,:root{--pico-font-size:106.25%}}@media (min-width:768px){:host,:root{--pico-font-size:112.5%}}@media (min-width:1024px){:host,:root{--pico-font-size:118.75%}}@media (min-width:1280px){:host,:root{--pico-font-size:125%}}@media (min-width:1536px){:host,:root{--pico-font-size:131.25%}}a{--pico-text-decoration:underline}a.contrast,a.secondary{--pico-text-decoration:underline}small{--pico-font-size:0.875em}h1,h2,h3,h4,h5,h6{--pico-font-weight:700}h1{--pico-font-size:2rem;--pico-line-height:1.125;--pico-typography-spacing-top:3rem}h2{--pico-font-size:1.75rem;--pico-line-height:1.15;--pico-typography-spacing-top:2.625rem}h3{--pico-font-size:1.5rem;--pico-line-height:1.175;--pico-typography-spacing-top:2.25rem}h4{--pico-font-size:1.25rem;--pico-line-height:1.2;--pico-typography-spacing-top:1.874rem}h5{--pico-font-size:1.125rem;--pico-line-height:1.225;--pico-typography-spacing-top:1.6875rem}h6{--pico-font-size:1rem;--pico-line-height:1.25;--pico-typography-spacing-top:1.5rem}tfoot td,tfoot th,thead td,thead th{--pico-font-weight:600;--pico-border-width:0.1875rem}code,kbd,pre,samp{--pico-font-family:var(--pico-font-family-monospace)}kbd{--pico-font-weight:bolder}:where(select,textarea),input:not([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]){--pico-outline-width:0.0625rem}[type=search]{--pico-border-radius:5rem}[type=checkbox],[type=radio]{--pico-border-width:0.125rem}[type=checkbox][role=switch]{--pico-border-width:0.1875rem}details.dropdown summary:not([role=button]){--pico-outline-width:0.0625rem}nav details.dropdown summary:focus-visible{--pico-outline-width:0.125rem}[role=search]{--pico-border-radius:5rem}[role=group]:has(button.secondary:focus,[type=submit].secondary:focus,[type=button].secondary:focus,[role=button].secondary:focus),[role=search]:has(button.secondary:focus,[type=submit].secondary:focus,[type=button].secondary:focus,[role=button].secondary:focus){--pico-group-box-shadow-focus-with-button:0 0 0 var(--pico-outline-width) var(--pico-secondary-focus)}[role=group]:has(button.contrast:focus,[type=submit].contrast:focus,[type=button].contrast:focus,[role=button].contrast:focus),[role=search]:has(button.contrast:focus,[type=submit].contrast:focus,[type=button].contrast:focus,[role=button].contrast:focus){--pico-group-box-shadow-focus-with-button:0 0 0 var(--pico-outline-width) var(--pico-contrast-focus)}[role=group] [role=button],[role=group] [type=button],[role=group] [type=submit],[role=group] button,[role=search] [role=button],[role=search] [type=button],[role=search] [type=submit],[role=search] button{--pico-form-element-spacing-horizontal:2rem}details summary[role=button]:not(.outline)::after{filter:brightness(0) invert(1)}[aria-busy=true]:not(input,select,textarea):is(button,[type=submit],[type=button],[type=reset],[role=button]):not(.outline)::before{filter:brightness(0) invert(1)}:host(:not([data-theme=dark])),:root:not([data-theme=dark]),[data-theme=light]{color-scheme:light;--pico-background-color:#fff;--pico-color:#373c44;--pico-text-selection-color:rgba(2, 154, 232, 0.25);--pico-muted-color:#646b79;--pico-muted-border-color:rgb(231, 234, 239.5);--pico-primary:#0172ad;--pico-primary-background:#0172ad;--pico-primary-border:var(--pico-primary-background);--pico-primary-underline:rgba(1, 114, 173, 0.5);--pico-primary-hover:#015887;--pico-primary-hover-background:#02659a;--pico-primary-hover-border:var(--pico-primary-hover-background);--pico-primary-hover-underline:var(--pico-primary-hover);--pico-primary-focus:rgba(2, 154, 232, 0.5);--pico-primary-inverse:#fff;--pico-secondary:#5d6b89;--pico-secondary-background:#525f7a;--pico-secondary-border:var(--pico-secondary-background);--pico-secondary-underline:rgba(93, 107, 137, 0.5);--pico-secondary-hover:#48536b;--pico-secondary-hover-background:#48536b;--pico-secondary-hover-border:var(--pico-secondary-hover-background);--pico-secondary-hover-underline:var(--pico-secondary-hover);--pico-secondary-focus:rgba(93, 107, 137, 0.25);--pico-secondary-inverse:#fff;--pico-contrast:#181c25;--pico-contrast-background:#181c25;--pico-contrast-border:var(--pico-contrast-background);--pico-contrast-underline:rgba(24, 28, 37, 0.5);--pico-contrast-hover:#000;--pico-contrast-hover-background:#000;--pico-contrast-hover-border:var(--pico-contrast-hover-background);--pico-contrast-hover-underline:var(--pico-secondary-hover);--pico-contrast-focus:rgba(93, 107, 137, 0.25);--pico-contrast-inverse:#fff;--pico-box-shadow:0.0145rem 0.029rem 0.174rem rgba(129, 145, 181, 0.01698),0.0335rem 0.067rem 0.402rem rgba(129, 145, 181, 0.024),0.0625rem 0.125rem 0.75rem rgba(129, 145, 181, 0.03),0.1125rem 0.225rem 1.35rem rgba(129, 145, 181, 0.036),0.2085rem 0.417rem 2.502rem rgba(129, 145, 181, 0.04302),0.5rem 1rem 6rem rgba(129, 145, 181, 0.06),0 0 0 0.0625rem rgba(129, 145, 181, 0.015);--pico-h1-color:#2d3138;--pico-h2-color:#373c44;--pico-h3-color:#424751;--pico-h4-color:#4d535e;--pico-h5-color:#5c6370;--pico-h6-color:#646b79;--pico-mark-background-color:rgb(252.5, 230.5, 191.5);--pico-mark-color:#0f1114;--pico-ins-color:rgb(28.5, 105.5, 84);--pico-del-color:rgb(136, 56.5, 53);--pico-blockquote-border-color:var(--pico-muted-border-color);--pico-blockquote-footer-color:var(--pico-muted-color);--pico-button-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-button-hover-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-table-border-color:var(--pico-muted-border-color);--pico-table-row-stripped-background-color:rgba(111, 120, 135, 0.0375);--pico-code-background-color:rgb(243, 244.5, 246.75);--pico-code-color:#646b79;--pico-code-kbd-background-color:var(--pico-color);--pico-code-kbd-color:var(--pico-background-color);--pico-form-element-background-color:rgb(251, 251.5, 252.25);--pico-form-element-selected-background-color:#dfe3eb;--pico-form-element-border-color:#cfd5e2;--pico-form-element-color:#23262c;--pico-form-element-placeholder-color:var(--pico-muted-color);--pico-form-element-active-background-color:#fff;--pico-form-element-active-border-color:var(--pico-primary-border);--pico-form-element-focus-color:var(--pico-primary-border);--pico-form-element-disabled-opacity:0.5;--pico-form-element-invalid-border-color:rgb(183.5, 105.5, 106.5);--pico-form-element-invalid-active-border-color:rgb(200.25, 79.25, 72.25);--pico-form-element-invalid-focus-color:var(--pico-form-element-invalid-active-border-color);--pico-form-element-valid-border-color:rgb(76, 154.5, 137.5);--pico-form-element-valid-active-border-color:rgb(39, 152.75, 118.75);--pico-form-element-valid-focus-color:var(--pico-form-element-valid-active-border-color);--pico-switch-background-color:#bfc7d9;--pico-switch-checked-background-color:var(--pico-primary-background);--pico-switch-color:#fff;--pico-switch-thumb-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-range-border-color:#dfe3eb;--pico-range-active-border-color:#bfc7d9;--pico-range-thumb-border-color:var(--pico-background-color);--pico-range-thumb-color:var(--pico-secondary-background);--pico-range-thumb-active-color:var(--pico-primary-background);--pico-accordion-border-color:var(--pico-muted-border-color);--pico-accordion-active-summary-color:var(--pico-primary-hover);--pico-accordion-close-summary-color:var(--pico-color);--pico-accordion-open-summary-color:var(--pico-muted-color);--pico-card-background-color:var(--pico-background-color);--pico-card-border-color:var(--pico-muted-border-color);--pico-card-box-shadow:var(--pico-box-shadow);--pico-card-sectioning-background-color:rgb(251, 251.5, 252.25);--pico-dropdown-background-color:#fff;--pico-dropdown-border-color:#eff1f4;--pico-dropdown-box-shadow:var(--pico-box-shadow);--pico-dropdown-color:var(--pico-color);--pico-dropdown-hover-background-color:#eff1f4;--pico-loading-spinner-opacity:0.5;--pico-modal-overlay-background-color:rgba(232, 234, 237, 0.75);--pico-progress-background-color:#dfe3eb;--pico-progress-color:var(--pico-primary-background);--pico-tooltip-background-color:var(--pico-contrast-background);--pico-tooltip-color:var(--pico-contrast-inverse);--pico-icon-valid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(76, 154.5, 137.5)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");--pico-icon-invalid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(200.25, 79.25, 72.25)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E")}:host(:not([data-theme=dark])) input:is([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]),:root:not([data-theme=dark]) input:is([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]),[data-theme=light] input:is([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]){--pico-form-element-focus-color:var(--pico-primary-focus)}@media only screen and (prefers-color-scheme:dark){:host(:not([data-theme])),:root:not([data-theme]){color-scheme:dark;--pico-background-color:rgb(19, 22.5, 30.5);--pico-color:#c2c7d0;--pico-text-selection-color:rgba(1, 170, 255, 0.1875);--pico-muted-color:#7b8495;--pico-muted-border-color:#202632;--pico-primary:#01aaff;--pico-primary-background:#0172ad;--pico-primary-border:var(--pico-primary-background);--pico-primary-underline:rgba(1, 170, 255, 0.5);--pico-primary-hover:#79c0ff;--pico-primary-hover-background:#017fc0;--pico-primary-hover-border:var(--pico-primary-hover-background);--pico-primary-hover-underline:var(--pico-primary-hover);--pico-primary-focus:rgba(1, 170, 255, 0.375);--pico-primary-inverse:#fff;--pico-secondary:#969eaf;--pico-secondary-background:#525f7a;--pico-secondary-border:var(--pico-secondary-background);--pico-secondary-underline:rgba(150, 158, 175, 0.5);--pico-secondary-hover:#b3b9c5;--pico-secondary-hover-background:#5d6b89;--pico-secondary-hover-border:var(--pico-secondary-hover-background);--pico-secondary-hover-underline:var(--pico-secondary-hover);--pico-secondary-focus:rgba(144, 158, 190, 0.25);--pico-secondary-inverse:#fff;--pico-contrast:#dfe3eb;--pico-contrast-background:#eff1f4;--pico-contrast-border:var(--pico-contrast-background);--pico-contrast-underline:rgba(223, 227, 235, 0.5);--pico-contrast-hover:#fff;--pico-contrast-hover-background:#fff;--pico-contrast-hover-border:var(--pico-contrast-hover-background);--pico-contrast-hover-underline:var(--pico-contrast-hover);--pico-contrast-focus:rgba(207, 213, 226, 0.25);--pico-contrast-inverse:#000;--pico-box-shadow:0.0145rem 0.029rem 0.174rem rgba(7, 8.5, 12, 0.01698),0.0335rem 0.067rem 0.402rem rgba(7, 8.5, 12, 0.024),0.0625rem 0.125rem 0.75rem rgba(7, 8.5, 12, 0.03),0.1125rem 0.225rem 1.35rem rgba(7, 8.5, 12, 0.036),0.2085rem 0.417rem 2.502rem rgba(7, 8.5, 12, 0.04302),0.5rem 1rem 6rem rgba(7, 8.5, 12, 0.06),0 0 0 0.0625rem rgba(7, 8.5, 12, 0.015);--pico-h1-color:#f0f1f3;--pico-h2-color:#e0e3e7;--pico-h3-color:#c2c7d0;--pico-h4-color:#b3b9c5;--pico-h5-color:#a4acba;--pico-h6-color:#8891a4;--pico-mark-background-color:#014063;--pico-mark-color:#fff;--pico-ins-color:#62af9a;--pico-del-color:rgb(205.5, 126, 123);--pico-blockquote-border-color:var(--pico-muted-border-color);--pico-blockquote-footer-color:var(--pico-muted-color);--pico-button-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-button-hover-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-table-border-color:var(--pico-muted-border-color);--pico-table-row-stripped-background-color:rgba(111, 120, 135, 0.0375);--pico-code-background-color:rgb(26, 30.5, 40.25);--pico-code-color:#8891a4;--pico-code-kbd-background-color:var(--pico-color);--pico-code-kbd-color:var(--pico-background-color);--pico-form-element-background-color:rgb(28, 33, 43.5);--pico-form-element-selected-background-color:#2a3140;--pico-form-element-border-color:#2a3140;--pico-form-element-color:#e0e3e7;--pico-form-element-placeholder-color:#8891a4;--pico-form-element-active-background-color:rgb(26, 30.5, 40.25);--pico-form-element-active-border-color:var(--pico-primary-border);--pico-form-element-focus-color:var(--pico-primary-border);--pico-form-element-disabled-opacity:0.5;--pico-form-element-invalid-border-color:rgb(149.5, 74, 80);--pico-form-element-invalid-active-border-color:rgb(183.25, 63.5, 59);--pico-form-element-invalid-focus-color:var(--pico-form-element-invalid-active-border-color);--pico-form-element-valid-border-color:#2a7b6f;--pico-form-element-valid-active-border-color:rgb(22, 137, 105.5);--pico-form-element-valid-focus-color:var(--pico-form-element-valid-active-border-color);--pico-switch-background-color:#333c4e;--pico-switch-checked-background-color:var(--pico-primary-background);--pico-switch-color:#fff;--pico-switch-thumb-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-range-border-color:#202632;--pico-range-active-border-color:#2a3140;--pico-range-thumb-border-color:var(--pico-background-color);--pico-range-thumb-color:var(--pico-secondary-background);--pico-range-thumb-active-color:var(--pico-primary-background);--pico-accordion-border-color:var(--pico-muted-border-color);--pico-accordion-active-summary-color:var(--pico-primary-hover);--pico-accordion-close-summary-color:var(--pico-color);--pico-accordion-open-summary-color:var(--pico-muted-color);--pico-card-background-color:#181c25;--pico-card-border-color:var(--pico-card-background-color);--pico-card-box-shadow:var(--pico-box-shadow);--pico-card-sectioning-background-color:rgb(26, 30.5, 40.25);--pico-dropdown-background-color:#181c25;--pico-dropdown-border-color:#202632;--pico-dropdown-box-shadow:var(--pico-box-shadow);--pico-dropdown-color:var(--pico-color);--pico-dropdown-hover-background-color:#202632;--pico-loading-spinner-opacity:0.5;--pico-modal-overlay-background-color:rgba(7.5, 8.5, 10, 0.75);--pico-progress-background-color:#202632;--pico-progress-color:var(--pico-primary-background);--pico-tooltip-background-color:var(--pico-contrast-background);--pico-tooltip-color:var(--pico-contrast-inverse);--pico-icon-valid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(42, 123, 111)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");--pico-icon-invalid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(149.5, 74, 80)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E")}:host(:not([data-theme])) input:is([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]),:root:not([data-theme]) input:is([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]){--pico-form-element-focus-color:var(--pico-primary-focus)}:host(:not([data-theme])) details summary[role=button].contrast:not(.outline)::after,:root:not([data-theme]) details summary[role=button].contrast:not(.outline)::after{filter:brightness(0)}:host(:not([data-theme])) [aria-busy=true]:not(input,select,textarea).contrast:is(button,[type=submit],[type=button],[type=reset],[role=button]):not(.outline)::before,:root:not([data-theme]) [aria-busy=true]:not(input,select,textarea).contrast:is(button,[type=submit],[type=button],[type=reset],[role=button]):not(.outline)::before{filter:brightness(0)}}[data-theme=dark]{color-scheme:dark;--pico-background-color:rgb(19, 22.5, 30.5);--pico-color:#c2c7d0;--pico-text-selection-color:rgba(1, 170, 255, 0.1875);--pico-muted-color:#7b8495;--pico-muted-border-color:#202632;--pico-primary:#01aaff;--pico-primary-background:#0172ad;--pico-primary-border:var(--pico-primary-background);--pico-primary-underline:rgba(1, 170, 255, 0.5);--pico-primary-hover:#79c0ff;--pico-primary-hover-background:#017fc0;--pico-primary-hover-border:var(--pico-primary-hover-background);--pico-primary-hover-underline:var(--pico-primary-hover);--pico-primary-focus:rgba(1, 170, 255, 0.375);--pico-primary-inverse:#fff;--pico-secondary:#969eaf;--pico-secondary-background:#525f7a;--pico-secondary-border:var(--pico-secondary-background);--pico-secondary-underline:rgba(150, 158, 175, 0.5);--pico-secondary-hover:#b3b9c5;--pico-secondary-hover-background:#5d6b89;--pico-secondary-hover-border:var(--pico-secondary-hover-background);--pico-secondary-hover-underline:var(--pico-secondary-hover);--pico-secondary-focus:rgba(144, 158, 190, 0.25);--pico-secondary-inverse:#fff;--pico-contrast:#dfe3eb;--pico-contrast-background:#eff1f4;--pico-contrast-border:var(--pico-contrast-background);--pico-contrast-underline:rgba(223, 227, 235, 0.5);--pico-contrast-hover:#fff;--pico-contrast-hover-background:#fff;--pico-contrast-hover-border:var(--pico-contrast-hover-background);--pico-contrast-hover-underline:var(--pico-contrast-hover);--pico-contrast-focus:rgba(207, 213, 226, 0.25);--pico-contrast-inverse:#000;--pico-box-shadow:0.0145rem 0.029rem 0.174rem rgba(7, 8.5, 12, 0.01698),0.0335rem 0.067rem 0.402rem rgba(7, 8.5, 12, 0.024),0.0625rem 0.125rem 0.75rem rgba(7, 8.5, 12, 0.03),0.1125rem 0.225rem 1.35rem rgba(7, 8.5, 12, 0.036),0.2085rem 0.417rem 2.502rem rgba(7, 8.5, 12, 0.04302),0.5rem 1rem 6rem rgba(7, 8.5, 12, 0.06),0 0 0 0.0625rem rgba(7, 8.5, 12, 0.015);--pico-h1-color:#f0f1f3;--pico-h2-color:#e0e3e7;--pico-h3-color:#c2c7d0;--pico-h4-color:#b3b9c5;--pico-h5-color:#a4acba;--pico-h6-color:#8891a4;--pico-mark-background-color:#014063;--pico-mark-color:#fff;--pico-ins-color:#62af9a;--pico-del-color:rgb(205.5, 126, 123);--pico-blockquote-border-color:var(--pico-muted-border-color);--pico-blockquote-footer-color:var(--pico-muted-color);--pico-button-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-button-hover-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-table-border-color:var(--pico-muted-border-color);--pico-table-row-stripped-background-color:rgba(111, 120, 135, 0.0375);--pico-code-background-color:rgb(26, 30.5, 40.25);--pico-code-color:#8891a4;--pico-code-kbd-background-color:var(--pico-color);--pico-code-kbd-color:var(--pico-background-color);--pico-form-element-background-color:rgb(28, 33, 43.5);--pico-form-element-selected-background-color:#2a3140;--pico-form-element-border-color:#2a3140;--pico-form-element-color:#e0e3e7;--pico-form-element-placeholder-color:#8891a4;--pico-form-element-active-background-color:rgb(26, 30.5, 40.25);--pico-form-element-active-border-color:var(--pico-primary-border);--pico-form-element-focus-color:var(--pico-primary-border);--pico-form-element-disabled-opacity:0.5;--pico-form-element-invalid-border-color:rgb(149.5, 74, 80);--pico-form-element-invalid-active-border-color:rgb(183.25, 63.5, 59);--pico-form-element-invalid-focus-color:var(--pico-form-element-invalid-active-border-color);--pico-form-element-valid-border-color:#2a7b6f;--pico-form-element-valid-active-border-color:rgb(22, 137, 105.5);--pico-form-element-valid-focus-color:var(--pico-form-element-valid-active-border-color);--pico-switch-background-color:#333c4e;--pico-switch-checked-background-color:var(--pico-primary-background);--pico-switch-color:#fff;--pico-switch-thumb-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-range-border-color:#202632;--pico-range-active-border-color:#2a3140;--pico-range-thumb-border-color:var(--pico-background-color);--pico-range-thumb-color:var(--pico-secondary-background);--pico-range-thumb-active-color:var(--pico-primary-background);--pico-accordion-border-color:var(--pico-muted-border-color);--pico-accordion-active-summary-color:var(--pico-primary-hover);--pico-accordion-close-summary-color:var(--pico-color);--pico-accordion-open-summary-color:var(--pico-muted-color);--pico-card-background-color:#181c25;--pico-card-border-color:var(--pico-card-background-color);--pico-card-box-shadow:var(--pico-box-shadow);--pico-card-sectioning-background-color:rgb(26, 30.5, 40.25);--pico-dropdown-background-color:#181c25;--pico-dropdown-border-color:#202632;--pico-dropdown-box-shadow:var(--pico-box-shadow);--pico-dropdown-color:var(--pico-color);--pico-dropdown-hover-background-color:#202632;--pico-loading-spinner-opacity:0.5;--pico-modal-overlay-background-color:rgba(7.5, 8.5, 10, 0.75);--pico-progress-background-color:#202632;--pico-progress-color:var(--pico-primary-background);--pico-tooltip-background-color:var(--pico-contrast-background);--pico-tooltip-color:var(--pico-contrast-inverse);--pico-icon-valid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(42, 123, 111)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");--pico-icon-invalid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(149.5, 74, 80)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E")}[data-theme=dark] input:is([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]){--pico-form-element-focus-color:var(--pico-primary-focus)}[data-theme=dark] details summary[role=button].contrast:not(.outline)::after{filter:brightness(0)}[data-theme=dark] [aria-busy=true]:not(input,select,textarea).contrast:is(button,[type=submit],[type=button],[type=reset],[role=button]):not(.outline)::before{filter:brightness(0)}[type=checkbox],[type=radio],[type=range],progress{accent-color:var(--pico-primary)}*,::after,::before{box-sizing:border-box;background-repeat:no-repeat}::after,::before{text-decoration:inherit;vertical-align:inherit}:where(:host),:where(:root){-webkit-tap-highlight-color:transparent;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;background-color:var(--pico-background-color);color:var(--pico-color);font-weight:var(--pico-font-weight);font-size:var(--pico-font-size);line-height:var(--pico-line-height);font-family:var(--pico-font-family);text-underline-offset:var(--pico-text-underline-offset);text-rendering:optimizeLegibility;overflow-wrap:break-word;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{width:100%;margin:0}main{display:block}body>footer,body>header,body>main{padding-block:var(--pico-block-spacing-vertical)}section{margin-bottom:var(--pico-block-spacing-vertical)}.container,.container-fluid{width:100%;margin-right:auto;margin-left:auto;padding-right:var(--pico-spacing);padding-left:var(--pico-spacing)}@media (min-width:576px){.container{max-width:510px;padding-right:0;padding-left:0}}@media (min-width:768px){.container{max-width:700px}}@media (min-width:1024px){.container{max-width:950px}}@media (min-width:1280px){.container{max-width:1200px}}@media (min-width:1536px){.container{max-width:1450px}}.grid{grid-column-gap:var(--pico-grid-column-gap);grid-row-gap:var(--pico-grid-row-gap);display:grid;grid-template-columns:1fr}@media (min-width:768px){.grid{grid-template-columns:repeat(auto-fit,minmax(0%,1fr))}}.grid>*{min-width:0}.overflow-auto{overflow:auto}b,strong{font-weight:bolder}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}address,blockquote,dl,ol,p,pre,table,ul{margin-top:0;margin-bottom:var(--pico-typography-spacing-vertical);color:var(--pico-color);font-style:normal;font-weight:var(--pico-font-weight)}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:var(--pico-typography-spacing-vertical);color:var(--pico-color);font-weight:var(--pico-font-weight);font-size:var(--pico-font-size);line-height:var(--pico-line-height);font-family:var(--pico-font-family)}h1{--pico-color:var(--pico-h1-color)}h2{--pico-color:var(--pico-h2-color)}h3{--pico-color:var(--pico-h3-color)}h4{--pico-color:var(--pico-h4-color)}h5{--pico-color:var(--pico-h5-color)}h6{--pico-color:var(--pico-h6-color)}:where(article,address,blockquote,dl,figure,form,ol,p,pre,table,ul)~:is(h1,h2,h3,h4,h5,h6){margin-top:var(--pico-typography-spacing-top)}p{margin-bottom:var(--pico-typography-spacing-vertical)}hgroup{margin-bottom:var(--pico-typography-spacing-vertical)}hgroup>*{margin-top:0;margin-bottom:0}hgroup>:not(:first-child):last-child{--pico-color:var(--pico-muted-color);--pico-font-weight:unset;font-size:1rem}:where(ol,ul) li{margin-bottom:calc(var(--pico-typography-spacing-vertical) * .25)}:where(dl,ol,ul) :where(dl,ol,ul){margin:0;margin-top:calc(var(--pico-typography-spacing-vertical) * .25)}ul li{list-style:square}mark{padding:.125rem .25rem;background-color:var(--pico-mark-background-color);color:var(--pico-mark-color);vertical-align:baseline}blockquote{display:block;margin:var(--pico-typography-spacing-vertical) 0;padding:var(--pico-spacing);border-right:none;border-left:.25rem solid var(--pico-blockquote-border-color);border-inline-start:0.25rem solid var(--pico-blockquote-border-color);border-inline-end:none}blockquote footer{margin-top:calc(var(--pico-typography-spacing-vertical) * .5);color:var(--pico-blockquote-footer-color)}abbr[title]{border-bottom:1px dotted;text-decoration:none;cursor:help}ins{color:var(--pico-ins-color);text-decoration:none}del{color:var(--pico-del-color)}::-moz-selection{background-color:var(--pico-text-selection-color)}::selection{background-color:var(--pico-text-selection-color)}:where(a:not([role=button])),[role=link]{--pico-color:var(--pico-primary);--pico-background-color:transparent;--pico-underline:var(--pico-primary-underline);outline:0;background-color:var(--pico-background-color);color:var(--pico-color);-webkit-text-decoration:var(--pico-text-decoration);text-decoration:var(--pico-text-decoration);text-decoration-color:var(--pico-underline);text-underline-offset:0.125em;transition:background-color var(--pico-transition),color var(--pico-transition),box-shadow var(--pico-transition),-webkit-text-decoration var(--pico-transition);transition:background-color var(--pico-transition),color var(--pico-transition),text-decoration var(--pico-transition),box-shadow var(--pico-transition);transition:background-color var(--pico-transition),color var(--pico-transition),text-decoration var(--pico-transition),box-shadow var(--pico-transition),-webkit-text-decoration var(--pico-transition)}:where(a:not([role=button])):is([aria-current]:not([aria-current=false]),:hover,:active,:focus),[role=link]:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){--pico-color:var(--pico-primary-hover);--pico-underline:var(--pico-primary-hover-underline);--pico-text-decoration:underline}:where(a:not([role=button])):focus-visible,[role=link]:focus-visible{box-shadow:0 0 0 var(--pico-outline-width) var(--pico-primary-focus)}:where(a:not([role=button])).secondary,[role=link].secondary{--pico-color:var(--pico-secondary);--pico-underline:var(--pico-secondary-underline)}:where(a:not([role=button])).secondary:is([aria-current]:not([aria-current=false]),:hover,:active,:focus),[role=link].secondary:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){--pico-color:var(--pico-secondary-hover);--pico-underline:var(--pico-secondary-hover-underline)}:where(a:not([role=button])).contrast,[role=link].contrast{--pico-color:var(--pico-contrast);--pico-underline:var(--pico-contrast-underline)}:where(a:not([role=button])).contrast:is([aria-current]:not([aria-current=false]),:hover,:active,:focus),[role=link].contrast:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){--pico-color:var(--pico-contrast-hover);--pico-underline:var(--pico-contrast-hover-underline)}a[role=button]{display:inline-block}button{margin:0;overflow:visible;font-family:inherit;text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[role=button],[type=button],[type=file]::file-selector-button,[type=reset],[type=submit],button{--pico-background-color:var(--pico-primary-background);--pico-border-color:var(--pico-primary-border);--pico-color:var(--pico-primary-inverse);--pico-box-shadow:var(--pico-button-box-shadow, 0 0 0 rgba(0, 0, 0, 0));padding:var(--pico-form-element-spacing-vertical) var(--pico-form-element-spacing-horizontal);border:var(--pico-border-width) solid var(--pico-border-color);border-radius:var(--pico-border-radius);outline:0;background-color:var(--pico-background-color);box-shadow:var(--pico-box-shadow);color:var(--pico-color);font-weight:var(--pico-font-weight);font-size:1rem;line-height:var(--pico-line-height);text-align:center;text-decoration:none;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;transition:background-color var(--pico-transition),border-color var(--pico-transition),color var(--pico-transition),box-shadow var(--pico-transition)}[role=button]:is(:hover,:active,:focus),[role=button]:is([aria-current]:not([aria-current=false])),[type=button]:is(:hover,:active,:focus),[type=button]:is([aria-current]:not([aria-current=false])),[type=file]::file-selector-button:is(:hover,:active,:focus),[type=file]::file-selector-button:is([aria-current]:not([aria-current=false])),[type=reset]:is(:hover,:active,:focus),[type=reset]:is([aria-current]:not([aria-current=false])),[type=submit]:is(:hover,:active,:focus),[type=submit]:is([aria-current]:not([aria-current=false])),button:is(:hover,:active,:focus),button:is([aria-current]:not([aria-current=false])){--pico-background-color:var(--pico-primary-hover-background);--pico-border-color:var(--pico-primary-hover-border);--pico-box-shadow:var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0));--pico-color:var(--pico-primary-inverse)}[role=button]:focus,[role=button]:is([aria-current]:not([aria-current=false])):focus,[type=button]:focus,[type=button]:is([aria-current]:not([aria-current=false])):focus,[type=file]::file-selector-button:focus,[type=file]::file-selector-button:is([aria-current]:not([aria-current=false])):focus,[type=reset]:focus,[type=reset]:is([aria-current]:not([aria-current=false])):focus,[type=submit]:focus,[type=submit]:is([aria-current]:not([aria-current=false])):focus,button:focus,button:is([aria-current]:not([aria-current=false])):focus{--pico-box-shadow:var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),0 0 0 var(--pico-outline-width) var(--pico-primary-focus)}[type=button],[type=reset],[type=submit]{margin-bottom:var(--pico-spacing)}:is(button,[type=submit],[type=button],[role=button]).secondary,[type=file]::file-selector-button,[type=reset]{--pico-background-color:var(--pico-secondary-background);--pico-border-color:var(--pico-secondary-border);--pico-color:var(--pico-secondary-inverse);cursor:pointer}:is(button,[type=submit],[type=button],[role=button]).secondary:is([aria-current]:not([aria-current=false]),:hover,:active,:focus),[type=file]::file-selector-button:is([aria-current]:not([aria-current=false]),:hover,:active,:focus),[type=reset]:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){--pico-background-color:var(--pico-secondary-hover-background);--pico-border-color:var(--pico-secondary-hover-border);--pico-color:var(--pico-secondary-inverse)}:is(button,[type=submit],[type=button],[role=button]).secondary:focus,:is(button,[type=submit],[type=button],[role=button]).secondary:is([aria-current]:not([aria-current=false])):focus,[type=file]::file-selector-button:focus,[type=file]::file-selector-button:is([aria-current]:not([aria-current=false])):focus,[type=reset]:focus,[type=reset]:is([aria-current]:not([aria-current=false])):focus{--pico-box-shadow:var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),0 0 0 var(--pico-outline-width) var(--pico-secondary-focus)}:is(button,[type=submit],[type=button],[role=button]).contrast{--pico-background-color:var(--pico-contrast-background);--pico-border-color:var(--pico-contrast-border);--pico-color:var(--pico-contrast-inverse)}:is(button,[type=submit],[type=button],[role=button]).contrast:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){--pico-background-color:var(--pico-contrast-hover-background);--pico-border-color:var(--pico-contrast-hover-border);--pico-color:var(--pico-contrast-inverse)}:is(button,[type=submit],[type=button],[role=button]).contrast:focus,:is(button,[type=submit],[type=button],[role=button]).contrast:is([aria-current]:not([aria-current=false])):focus{--pico-box-shadow:var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),0 0 0 var(--pico-outline-width) var(--pico-contrast-focus)}:is(button,[type=submit],[type=button],[role=button]).outline,[type=reset].outline{--pico-background-color:transparent;--pico-color:var(--pico-primary);--pico-border-color:var(--pico-primary)}:is(button,[type=submit],[type=button],[role=button]).outline:is([aria-current]:not([aria-current=false]),:hover,:active,:focus),[type=reset].outline:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){--pico-background-color:transparent;--pico-color:var(--pico-primary-hover);--pico-border-color:var(--pico-primary-hover)}:is(button,[type=submit],[type=button],[role=button]).outline.secondary,[type=reset].outline{--pico-color:var(--pico-secondary);--pico-border-color:var(--pico-secondary)}:is(button,[type=submit],[type=button],[role=button]).outline.secondary:is([aria-current]:not([aria-current=false]),:hover,:active,:focus),[type=reset].outline:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){--pico-color:var(--pico-secondary-hover);--pico-border-color:var(--pico-secondary-hover)}:is(button,[type=submit],[type=button],[role=button]).outline.contrast{--pico-color:var(--pico-contrast);--pico-border-color:var(--pico-contrast)}:is(button,[type=submit],[type=button],[role=button]).outline.contrast:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){--pico-color:var(--pico-contrast-hover);--pico-border-color:var(--pico-contrast-hover)}:where(button,[type=submit],[type=reset],[type=button],[role=button])[disabled],:where(fieldset[disabled]) :is(button,[type=submit],[type=button],[type=reset],[role=button]){opacity:.5;pointer-events:none}:where(table){width:100%;border-collapse:collapse;border-spacing:0;text-indent:0}td,th{padding:calc(var(--pico-spacing)/ 2) var(--pico-spacing);border-bottom:var(--pico-border-width) solid var(--pico-table-border-color);background-color:var(--pico-background-color);color:var(--pico-color);font-weight:var(--pico-font-weight);text-align:left;text-align:start}tfoot td,tfoot th{border-top:var(--pico-border-width) solid var(--pico-table-border-color);border-bottom:0}table.striped tbody tr:nth-child(odd) td,table.striped tbody tr:nth-child(odd) th{background-color:var(--pico-table-row-stripped-background-color)}:where(audio,canvas,iframe,img,svg,video){vertical-align:middle}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}:where(iframe){border-style:none}img{max-width:100%;height:auto;border-style:none}:where(svg:not([fill])){fill:currentColor}svg:not(:host),svg:not(:root){overflow:hidden}code,kbd,pre,samp{font-size:.875em;font-family:var(--pico-font-family)}pre code,pre samp{font-size:inherit;font-family:inherit}pre{-ms-overflow-style:scrollbar;overflow:auto}code,kbd,pre,samp{border-radius:var(--pico-border-radius);background:var(--pico-code-background-color);color:var(--pico-code-color);font-weight:var(--pico-font-weight);line-height:initial}code,kbd,samp{display:inline-block;padding:.375rem}pre{display:block;margin-bottom:var(--pico-spacing);overflow-x:auto}pre>code,pre>samp{display:block;padding:var(--pico-spacing);background:0 0;line-height:var(--pico-line-height)}kbd{background-color:var(--pico-code-kbd-background-color);color:var(--pico-code-kbd-color);vertical-align:baseline}figure{display:block;margin:0;padding:0}figure figcaption{padding:calc(var(--pico-spacing) * .5) 0;color:var(--pico-muted-color)}hr{height:0;margin:var(--pico-typography-spacing-vertical) 0;border:0;border-top:1px solid var(--pico-muted-border-color);color:inherit}[hidden],template{display:none!important}canvas{display:inline-block}input,optgroup,select,textarea{margin:0;font-size:1rem;line-height:var(--pico-line-height);font-family:inherit;letter-spacing:inherit}input{overflow:visible}select{text-transform:none}legend{max-width:100%;padding:0;color:inherit;white-space:normal}textarea{overflow:auto}[type=checkbox],[type=radio]{padding:0}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}::-moz-focus-inner{padding:0;border-style:none}:-moz-focusring{outline:0}:-moz-ui-invalid{box-shadow:none}::-ms-expand{display:none}[type=file],[type=range]{padding:0;border-width:0}input:not([type=checkbox],[type=radio],[type=range]){height:calc(1rem * var(--pico-line-height) + var(--pico-form-element-spacing-vertical) * 2 + var(--pico-border-width) * 2)}fieldset{width:100%;margin:0;margin-bottom:var(--pico-spacing);padding:0;border:0}fieldset legend,label{display:block;margin-bottom:calc(var(--pico-spacing) * .375);color:var(--pico-color);font-weight:var(--pico-form-label-font-weight,var(--pico-font-weight))}fieldset legend{margin-bottom:calc(var(--pico-spacing) * .5)}button[type=submit],input:not([type=checkbox],[type=radio]),select,textarea{width:100%}input:not([type=checkbox],[type=radio],[type=range],[type=file]),select,textarea{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:var(--pico-form-element-spacing-vertical) var(--pico-form-element-spacing-horizontal)}input,select,textarea{--pico-background-color:var(--pico-form-element-background-color);--pico-border-color:var(--pico-form-element-border-color);--pico-color:var(--pico-form-element-color);--pico-box-shadow:none;border:var(--pico-border-width) solid var(--pico-border-color);border-radius:var(--pico-border-radius);outline:0;background-color:var(--pico-background-color);box-shadow:var(--pico-box-shadow);color:var(--pico-color);font-weight:var(--pico-font-weight);transition:background-color var(--pico-transition),border-color var(--pico-transition),color var(--pico-transition),box-shadow var(--pico-transition)}:where(select,textarea):not([readonly]):is(:active,:focus),input:not([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[readonly]):is(:active,:focus){--pico-background-color:var(--pico-form-element-active-background-color)}:where(select,textarea):not([readonly]):is(:active,:focus),input:not([type=submit],[type=button],[type=reset],[role=switch],[readonly]):is(:active,:focus){--pico-border-color:var(--pico-form-element-active-border-color)}:where(select,textarea):not([readonly]):focus,input:not([type=submit],[type=button],[type=reset],[type=range],[type=file],[readonly]):focus{--pico-box-shadow:0 0 0 var(--pico-outline-width) var(--pico-form-element-focus-color)}:where(fieldset[disabled]) :is(input:not([type=submit],[type=button],[type=reset]),select,textarea),input:not([type=submit],[type=button],[type=reset])[disabled],label[aria-disabled=true],select[disabled],textarea[disabled]{opacity:var(--pico-form-element-disabled-opacity);pointer-events:none}label[aria-disabled=true] input[disabled]{opacity:1}:where(input,select,textarea):not([type=checkbox],[type=radio],[type=date],[type=datetime-local],[type=month],[type=time],[type=week],[type=range])[aria-invalid]{padding-right:calc(var(--pico-form-element-spacing-horizontal) + 1.5rem)!important;padding-left:var(--pico-form-element-spacing-horizontal);padding-inline-start:var(--pico-form-element-spacing-horizontal)!important;padding-inline-end:calc(var(--pico-form-element-spacing-horizontal) + 1.5rem)!important;background-position:center right .75rem;background-size:1rem auto;background-repeat:no-repeat}:where(input,select,textarea):not([type=checkbox],[type=radio],[type=date],[type=datetime-local],[type=month],[type=time],[type=week],[type=range])[aria-invalid=false]:not(select){background-image:var(--pico-icon-valid)}:where(input,select,textarea):not([type=checkbox],[type=radio],[type=date],[type=datetime-local],[type=month],[type=time],[type=week],[type=range])[aria-invalid=true]:not(select){background-image:var(--pico-icon-invalid)}:where(input,select,textarea)[aria-invalid=false]{--pico-border-color:var(--pico-form-element-valid-border-color)}:where(input,select,textarea)[aria-invalid=false]:is(:active,:focus){--pico-border-color:var(--pico-form-element-valid-active-border-color)!important}:where(input,select,textarea)[aria-invalid=false]:is(:active,:focus):not([type=checkbox],[type=radio]){--pico-box-shadow:0 0 0 var(--pico-outline-width) var(--pico-form-element-valid-focus-color)!important}:where(input,select,textarea)[aria-invalid=true]{--pico-border-color:var(--pico-form-element-invalid-border-color)}:where(input,select,textarea)[aria-invalid=true]:is(:active,:focus){--pico-border-color:var(--pico-form-element-invalid-active-border-color)!important}:where(input,select,textarea)[aria-invalid=true]:is(:active,:focus):not([type=checkbox],[type=radio]){--pico-box-shadow:0 0 0 var(--pico-outline-width) var(--pico-form-element-invalid-focus-color)!important}[dir=rtl] :where(input,select,textarea):not([type=checkbox],[type=radio]):is([aria-invalid],[aria-invalid=true],[aria-invalid=false]){background-position:center left .75rem}input::-webkit-input-placeholder,input::placeholder,select:invalid,textarea::-webkit-input-placeholder,textarea::placeholder{color:var(--pico-form-element-placeholder-color);opacity:1}input:not([type=checkbox],[type=radio]),select,textarea{margin-bottom:var(--pico-spacing)}select::-ms-expand{border:0;background-color:transparent}select:not([multiple],[size]){padding-right:calc(var(--pico-form-element-spacing-horizontal) + 1.5rem);padding-left:var(--pico-form-element-spacing-horizontal);padding-inline-start:var(--pico-form-element-spacing-horizontal);padding-inline-end:calc(var(--pico-form-element-spacing-horizontal) + 1.5rem);background-image:var(--pico-icon-chevron);background-position:center right .75rem;background-size:1rem auto;background-repeat:no-repeat}select[multiple] option:checked{background:var(--pico-form-element-selected-background-color);color:var(--pico-form-element-color)}[dir=rtl] select:not([multiple],[size]){background-position:center left .75rem}textarea{display:block;resize:vertical}textarea[aria-invalid]{--pico-icon-height:calc(1rem * var(--pico-line-height) + var(--pico-form-element-spacing-vertical) * 2 + var(--pico-border-width) * 2);background-position:top right .75rem!important;background-size:1rem var(--pico-icon-height)!important}:where(input,select,textarea,fieldset,.grid)+small{display:block;width:100%;margin-top:calc(var(--pico-spacing) * -.75);margin-bottom:var(--pico-spacing);color:var(--pico-muted-color)}:where(input,select,textarea,fieldset,.grid)[aria-invalid=false]+small{color:var(--pico-ins-color)}:where(input,select,textarea,fieldset,.grid)[aria-invalid=true]+small{color:var(--pico-del-color)}label>:where(input,select,textarea){margin-top:calc(var(--pico-spacing) * .25)}label:has([type=checkbox],[type=radio]){width:-moz-fit-content;width:fit-content;cursor:pointer}[type=checkbox],[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:1.25em;height:1.25em;margin-top:-.125em;margin-inline-end:.5em;border-width:var(--pico-border-width);vertical-align:middle;cursor:pointer}[type=checkbox]::-ms-check,[type=radio]::-ms-check{display:none}[type=checkbox]:checked,[type=checkbox]:checked:active,[type=checkbox]:checked:focus,[type=radio]:checked,[type=radio]:checked:active,[type=radio]:checked:focus{--pico-background-color:var(--pico-primary-background);--pico-border-color:var(--pico-primary-border);background-image:var(--pico-icon-checkbox);background-position:center;background-size:.75em auto;background-repeat:no-repeat}[type=checkbox]~label,[type=radio]~label{display:inline-block;margin-bottom:0;cursor:pointer}[type=checkbox]~label:not(:last-of-type),[type=radio]~label:not(:last-of-type){margin-inline-end:1em}[type=checkbox]:indeterminate{--pico-background-color:var(--pico-primary-background);--pico-border-color:var(--pico-primary-border);background-image:var(--pico-icon-minus);background-position:center;background-size:.75em auto;background-repeat:no-repeat}[type=radio]{border-radius:50%}[type=radio]:checked,[type=radio]:checked:active,[type=radio]:checked:focus{--pico-background-color:var(--pico-primary-inverse);border-width:.35em;background-image:none}[type=checkbox][role=switch]{--pico-background-color:var(--pico-switch-background-color);--pico-color:var(--pico-switch-color);width:2.25em;height:1.25em;border:var(--pico-border-width) solid var(--pico-border-color);border-radius:1.25em;background-color:var(--pico-background-color);line-height:1.25em}[type=checkbox][role=switch]:not([aria-invalid]){--pico-border-color:var(--pico-switch-background-color)}[type=checkbox][role=switch]:before{display:block;aspect-ratio:1;height:100%;border-radius:50%;background-color:var(--pico-color);box-shadow:var(--pico-switch-thumb-box-shadow);content:"";transition:margin .1s ease-in-out}[type=checkbox][role=switch]:focus{--pico-background-color:var(--pico-switch-background-color);--pico-border-color:var(--pico-switch-background-color)}[type=checkbox][role=switch]:checked{--pico-background-color:var(--pico-switch-checked-background-color);--pico-border-color:var(--pico-switch-checked-background-color);background-image:none}[type=checkbox][role=switch]:checked::before{margin-inline-start:calc(2.25em - 1.25em)}[type=checkbox][role=switch][disabled]{--pico-background-color:var(--pico-border-color)}[type=checkbox][aria-invalid=false]:checked,[type=checkbox][aria-invalid=false]:checked:active,[type=checkbox][aria-invalid=false]:checked:focus,[type=checkbox][role=switch][aria-invalid=false]:checked,[type=checkbox][role=switch][aria-invalid=false]:checked:active,[type=checkbox][role=switch][aria-invalid=false]:checked:focus{--pico-background-color:var(--pico-form-element-valid-border-color)}[type=checkbox]:checked:active[aria-invalid=true],[type=checkbox]:checked:focus[aria-invalid=true],[type=checkbox]:checked[aria-invalid=true],[type=checkbox][role=switch]:checked:active[aria-invalid=true],[type=checkbox][role=switch]:checked:focus[aria-invalid=true],[type=checkbox][role=switch]:checked[aria-invalid=true]{--pico-background-color:var(--pico-form-element-invalid-border-color)}[type=checkbox][aria-invalid=false]:checked,[type=checkbox][aria-invalid=false]:checked:active,[type=checkbox][aria-invalid=false]:checked:focus,[type=checkbox][role=switch][aria-invalid=false]:checked,[type=checkbox][role=switch][aria-invalid=false]:checked:active,[type=checkbox][role=switch][aria-invalid=false]:checked:focus,[type=radio][aria-invalid=false]:checked,[type=radio][aria-invalid=false]:checked:active,[type=radio][aria-invalid=false]:checked:focus{--pico-border-color:var(--pico-form-element-valid-border-color)}[type=checkbox]:checked:active[aria-invalid=true],[type=checkbox]:checked:focus[aria-invalid=true],[type=checkbox]:checked[aria-invalid=true],[type=checkbox][role=switch]:checked:active[aria-invalid=true],[type=checkbox][role=switch]:checked:focus[aria-invalid=true],[type=checkbox][role=switch]:checked[aria-invalid=true],[type=radio]:checked:active[aria-invalid=true],[type=radio]:checked:focus[aria-invalid=true],[type=radio]:checked[aria-invalid=true]{--pico-border-color:var(--pico-form-element-invalid-border-color)}[type=color]::-webkit-color-swatch-wrapper{padding:0}[type=color]::-moz-focus-inner{padding:0}[type=color]::-webkit-color-swatch{border:0;border-radius:calc(var(--pico-border-radius) * .5)}[type=color]::-moz-color-swatch{border:0;border-radius:calc(var(--pico-border-radius) * .5)}input:not([type=checkbox],[type=radio],[type=range],[type=file]):is([type=date],[type=datetime-local],[type=month],[type=time],[type=week]){--pico-icon-position:0.75rem;--pico-icon-width:1rem;padding-right:calc(var(--pico-icon-width) + var(--pico-icon-position));background-image:var(--pico-icon-date);background-position:center right var(--pico-icon-position);background-size:var(--pico-icon-width) auto;background-repeat:no-repeat}input:not([type=checkbox],[type=radio],[type=range],[type=file])[type=time]{background-image:var(--pico-icon-time)}[type=date]::-webkit-calendar-picker-indicator,[type=datetime-local]::-webkit-calendar-picker-indicator,[type=month]::-webkit-calendar-picker-indicator,[type=time]::-webkit-calendar-picker-indicator,[type=week]::-webkit-calendar-picker-indicator{width:var(--pico-icon-width);margin-right:calc(var(--pico-icon-width) * -1);margin-left:var(--pico-icon-position);opacity:0}@-moz-document url-prefix(){[type=date],[type=datetime-local],[type=month],[type=time],[type=week]{padding-right:var(--pico-form-element-spacing-horizontal)!important;background-image:none!important}}[dir=rtl] :is([type=date],[type=datetime-local],[type=month],[type=time],[type=week]){text-align:right}[type=file]{--pico-color:var(--pico-muted-color);margin-left:calc(var(--pico-outline-width) * -1);padding:calc(var(--pico-form-element-spacing-vertical) * .5) 0;padding-left:var(--pico-outline-width);border:0;border-radius:0;background:0 0}[type=file]::file-selector-button{margin-right:calc(var(--pico-spacing)/ 2);padding:calc(var(--pico-form-element-spacing-vertical) * .5) var(--pico-form-element-spacing-horizontal)}[type=file]:is(:hover,:active,:focus)::file-selector-button{--pico-background-color:var(--pico-secondary-hover-background);--pico-border-color:var(--pico-secondary-hover-border)}[type=file]:focus::file-selector-button{--pico-box-shadow:var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),0 0 0 var(--pico-outline-width) var(--pico-secondary-focus)}[type=range]{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:100%;height:1.25rem;background:0 0}[type=range]::-webkit-slider-runnable-track{width:100%;height:.375rem;border-radius:var(--pico-border-radius);background-color:var(--pico-range-border-color);-webkit-transition:background-color var(--pico-transition),box-shadow var(--pico-transition);transition:background-color var(--pico-transition),box-shadow var(--pico-transition)}[type=range]::-moz-range-track{width:100%;height:.375rem;border-radius:var(--pico-border-radius);background-color:var(--pico-range-border-color);-moz-transition:background-color var(--pico-transition),box-shadow var(--pico-transition);transition:background-color var(--pico-transition),box-shadow var(--pico-transition)}[type=range]::-ms-track{width:100%;height:.375rem;border-radius:var(--pico-border-radius);background-color:var(--pico-range-border-color);-ms-transition:background-color var(--pico-transition),box-shadow var(--pico-transition);transition:background-color var(--pico-transition),box-shadow var(--pico-transition)}[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:1.25rem;height:1.25rem;margin-top:-.4375rem;border:2px solid var(--pico-range-thumb-border-color);border-radius:50%;background-color:var(--pico-range-thumb-color);cursor:pointer;-webkit-transition:background-color var(--pico-transition),transform var(--pico-transition);transition:background-color var(--pico-transition),transform var(--pico-transition)}[type=range]::-moz-range-thumb{-webkit-appearance:none;width:1.25rem;height:1.25rem;margin-top:-.4375rem;border:2px solid var(--pico-range-thumb-border-color);border-radius:50%;background-color:var(--pico-range-thumb-color);cursor:pointer;-moz-transition:background-color var(--pico-transition),transform var(--pico-transition);transition:background-color var(--pico-transition),transform var(--pico-transition)}[type=range]::-ms-thumb{-webkit-appearance:none;width:1.25rem;height:1.25rem;margin-top:-.4375rem;border:2px solid var(--pico-range-thumb-border-color);border-radius:50%;background-color:var(--pico-range-thumb-color);cursor:pointer;-ms-transition:background-color var(--pico-transition),transform var(--pico-transition);transition:background-color var(--pico-transition),transform var(--pico-transition)}[type=range]:active,[type=range]:focus-within{--pico-range-border-color:var(--pico-range-active-border-color);--pico-range-thumb-color:var(--pico-range-thumb-active-color)}[type=range]:active::-webkit-slider-thumb{transform:scale(1.25)}[type=range]:active::-moz-range-thumb{transform:scale(1.25)}[type=range]:active::-ms-thumb{transform:scale(1.25)}input:not([type=checkbox],[type=radio],[type=range],[type=file])[type=search]{padding-inline-start:calc(var(--pico-form-element-spacing-horizontal) + 1.75rem);background-image:var(--pico-icon-search);background-position:center left calc(var(--pico-form-element-spacing-horizontal) + .125rem);background-size:1rem auto;background-repeat:no-repeat}input:not([type=checkbox],[type=radio],[type=range],[type=file])[type=search][aria-invalid]{padding-inline-start:calc(var(--pico-form-element-spacing-horizontal) + 1.75rem)!important;background-position:center left 1.125rem,center right .75rem}input:not([type=checkbox],[type=radio],[type=range],[type=file])[type=search][aria-invalid=false]{background-image:var(--pico-icon-search),var(--pico-icon-valid)}input:not([type=checkbox],[type=radio],[type=range],[type=file])[type=search][aria-invalid=true]{background-image:var(--pico-icon-search),var(--pico-icon-invalid)}[dir=rtl] :where(input):not([type=checkbox],[type=radio],[type=range],[type=file])[type=search]{background-position:center right 1.125rem}[dir=rtl] :where(input):not([type=checkbox],[type=radio],[type=range],[type=file])[type=search][aria-invalid]{background-position:center right 1.125rem,center left .75rem}details{display:block;margin-bottom:var(--pico-spacing)}details summary{line-height:1rem;list-style-type:none;cursor:pointer;transition:color var(--pico-transition)}details summary:not([role]){color:var(--pico-accordion-close-summary-color)}details summary::-webkit-details-marker{display:none}details summary::marker{display:none}details summary::-moz-list-bullet{list-style-type:none}details summary::after{display:block;width:1rem;height:1rem;margin-inline-start:calc(var(--pico-spacing,1rem) * .5);float:right;transform:rotate(-90deg);background-image:var(--pico-icon-chevron);background-position:right center;background-size:1rem auto;background-repeat:no-repeat;content:"";transition:transform var(--pico-transition)}details summary:focus{outline:0}details summary:focus:not([role]){color:var(--pico-accordion-active-summary-color)}details summary:focus-visible:not([role]){outline:var(--pico-outline-width) solid var(--pico-primary-focus);outline-offset:calc(var(--pico-spacing,1rem) * 0.5);color:var(--pico-primary)}details summary[role=button]{width:100%;text-align:left}details summary[role=button]::after{height:calc(1rem * var(--pico-line-height,1.5))}details[open]>summary{margin-bottom:var(--pico-spacing)}details[open]>summary:not([role]):not(:focus){color:var(--pico-accordion-open-summary-color)}details[open]>summary::after{transform:rotate(0)}[dir=rtl] details summary{text-align:right}[dir=rtl] details summary::after{float:left;background-position:left center}article{margin-bottom:var(--pico-block-spacing-vertical);padding:var(--pico-block-spacing-vertical) var(--pico-block-spacing-horizontal);border-radius:var(--pico-border-radius);background:var(--pico-card-background-color);box-shadow:var(--pico-card-box-shadow)}article>footer,article>header{margin-right:calc(var(--pico-block-spacing-horizontal) * -1);margin-left:calc(var(--pico-block-spacing-horizontal) * -1);padding:calc(var(--pico-block-spacing-vertical) * .66) var(--pico-block-spacing-horizontal);background-color:var(--pico-card-sectioning-background-color)}article>header{margin-top:calc(var(--pico-block-spacing-vertical) * -1);margin-bottom:var(--pico-block-spacing-vertical);border-bottom:var(--pico-border-width) solid var(--pico-card-border-color);border-top-right-radius:var(--pico-border-radius);border-top-left-radius:var(--pico-border-radius)}article>footer{margin-top:var(--pico-block-spacing-vertical);margin-bottom:calc(var(--pico-block-spacing-vertical) * -1);border-top:var(--pico-border-width) solid var(--pico-card-border-color);border-bottom-right-radius:var(--pico-border-radius);border-bottom-left-radius:var(--pico-border-radius)}details.dropdown{position:relative;border-bottom:none}details.dropdown>a::after,details.dropdown>button::after,details.dropdown>summary::after{display:block;width:1rem;height:calc(1rem * var(--pico-line-height,1.5));margin-inline-start:.25rem;float:right;transform:rotate(0) translateX(.2rem);background-image:var(--pico-icon-chevron);background-position:right center;background-size:1rem auto;background-repeat:no-repeat;content:""}nav details.dropdown{margin-bottom:0}details.dropdown>summary:not([role]){height:calc(1rem * var(--pico-line-height) + var(--pico-form-element-spacing-vertical) * 2 + var(--pico-border-width) * 2);padding:var(--pico-form-element-spacing-vertical) var(--pico-form-element-spacing-horizontal);border:var(--pico-border-width) solid var(--pico-form-element-border-color);border-radius:var(--pico-border-radius);background-color:var(--pico-form-element-background-color);color:var(--pico-form-element-placeholder-color);line-height:inherit;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;transition:background-color var(--pico-transition),border-color var(--pico-transition),color var(--pico-transition),box-shadow var(--pico-transition)}details.dropdown>summary:not([role]):active,details.dropdown>summary:not([role]):focus{border-color:var(--pico-form-element-active-border-color);background-color:var(--pico-form-element-active-background-color)}details.dropdown>summary:not([role]):focus{box-shadow:0 0 0 var(--pico-outline-width) var(--pico-form-element-focus-color)}details.dropdown>summary:not([role]):focus-visible{outline:0}details.dropdown>summary:not([role])[aria-invalid=false]{--pico-form-element-border-color:var(--pico-form-element-valid-border-color);--pico-form-element-active-border-color:var(--pico-form-element-valid-focus-color);--pico-form-element-focus-color:var(--pico-form-element-valid-focus-color)}details.dropdown>summary:not([role])[aria-invalid=true]{--pico-form-element-border-color:var(--pico-form-element-invalid-border-color);--pico-form-element-active-border-color:var(--pico-form-element-invalid-focus-color);--pico-form-element-focus-color:var(--pico-form-element-invalid-focus-color)}nav details.dropdown{display:inline;margin:calc(var(--pico-nav-element-spacing-vertical) * -1) 0}nav details.dropdown>summary::after{transform:rotate(0) translateX(0)}nav details.dropdown>summary:not([role]){height:calc(1rem * var(--pico-line-height) + var(--pico-nav-link-spacing-vertical) * 2);padding:calc(var(--pico-nav-link-spacing-vertical) - var(--pico-border-width) * 2) var(--pico-nav-link-spacing-horizontal)}nav details.dropdown>summary:not([role]):focus-visible{box-shadow:0 0 0 var(--pico-outline-width) var(--pico-primary-focus)}details.dropdown>summary+ul{display:flex;z-index:99;position:absolute;left:0;flex-direction:column;width:100%;min-width:-moz-fit-content;min-width:fit-content;margin:0;margin-top:var(--pico-outline-width);padding:0;border:var(--pico-border-width) solid var(--pico-dropdown-border-color);border-radius:var(--pico-border-radius);background-color:var(--pico-dropdown-background-color);box-shadow:var(--pico-dropdown-box-shadow);color:var(--pico-dropdown-color);white-space:nowrap;opacity:0;transition:opacity var(--pico-transition),transform 0s ease-in-out 1s}details.dropdown>summary+ul[dir=rtl]{right:0;left:auto}details.dropdown>summary+ul li{width:100%;margin-bottom:0;padding:calc(var(--pico-form-element-spacing-vertical) * .5) var(--pico-form-element-spacing-horizontal);list-style:none}details.dropdown>summary+ul li:first-of-type{margin-top:calc(var(--pico-form-element-spacing-vertical) * .5)}details.dropdown>summary+ul li:last-of-type{margin-bottom:calc(var(--pico-form-element-spacing-vertical) * .5)}details.dropdown>summary+ul li a{display:block;margin:calc(var(--pico-form-element-spacing-vertical) * -.5) calc(var(--pico-form-element-spacing-horizontal) * -1);padding:calc(var(--pico-form-element-spacing-vertical) * .5) var(--pico-form-element-spacing-horizontal);overflow:hidden;border-radius:0;color:var(--pico-dropdown-color);text-decoration:none;text-overflow:ellipsis}details.dropdown>summary+ul li a:active,details.dropdown>summary+ul li a:focus,details.dropdown>summary+ul li a:focus-visible,details.dropdown>summary+ul li a:hover,details.dropdown>summary+ul li a[aria-current]:not([aria-current=false]){background-color:var(--pico-dropdown-hover-background-color)}details.dropdown>summary+ul li label{width:100%}details.dropdown>summary+ul li:has(label):hover{background-color:var(--pico-dropdown-hover-background-color)}details.dropdown[open]>summary{margin-bottom:0}details.dropdown[open]>summary+ul{transform:scaleY(1);opacity:1;transition:opacity var(--pico-transition),transform 0s ease-in-out 0s}details.dropdown[open]>summary::before{display:block;z-index:1;position:fixed;width:100vw;height:100vh;inset:0;background:0 0;content:"";cursor:default}label>details.dropdown{margin-top:calc(var(--pico-spacing) * .25)}[role=group],[role=search]{display:inline-flex;position:relative;width:100%;margin-bottom:var(--pico-spacing);border-radius:var(--pico-border-radius);box-shadow:var(--pico-group-box-shadow,0 0 0 transparent);vertical-align:middle;transition:box-shadow var(--pico-transition)}[role=group] input:not([type=checkbox],[type=radio]),[role=group] select,[role=group]>*,[role=search] input:not([type=checkbox],[type=radio]),[role=search] select,[role=search]>*{position:relative;flex:1 1 auto;margin-bottom:0}[role=group] input:not([type=checkbox],[type=radio]):not(:first-child),[role=group] select:not(:first-child),[role=group]>:not(:first-child),[role=search] input:not([type=checkbox],[type=radio]):not(:first-child),[role=search] select:not(:first-child),[role=search]>:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}[role=group] input:not([type=checkbox],[type=radio]):not(:last-child),[role=group] select:not(:last-child),[role=group]>:not(:last-child),[role=search] input:not([type=checkbox],[type=radio]):not(:last-child),[role=search] select:not(:last-child),[role=search]>:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}[role=group] input:not([type=checkbox],[type=radio]):focus,[role=group] select:focus,[role=group]>:focus,[role=search] input:not([type=checkbox],[type=radio]):focus,[role=search] select:focus,[role=search]>:focus{z-index:2}[role=group] [role=button]:not(:first-child),[role=group] [type=button]:not(:first-child),[role=group] [type=reset]:not(:first-child),[role=group] [type=submit]:not(:first-child),[role=group] button:not(:first-child),[role=group] input:not([type=checkbox],[type=radio]):not(:first-child),[role=group] select:not(:first-child),[role=search] [role=button]:not(:first-child),[role=search] [type=button]:not(:first-child),[role=search] [type=reset]:not(:first-child),[role=search] [type=submit]:not(:first-child),[role=search] button:not(:first-child),[role=search] input:not([type=checkbox],[type=radio]):not(:first-child),[role=search] select:not(:first-child){margin-left:calc(var(--pico-border-width) * -1)}[role=group] [role=button],[role=group] [type=button],[role=group] [type=reset],[role=group] [type=submit],[role=group] button,[role=search] [role=button],[role=search] [type=button],[role=search] [type=reset],[role=search] [type=submit],[role=search] button{width:auto}@supports selector(:has(*)){[role=group]:has(button:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus),[role=search]:has(button:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus){--pico-group-box-shadow:var(--pico-group-box-shadow-focus-with-button)}[role=group]:has(button:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus) input:not([type=checkbox],[type=radio]),[role=group]:has(button:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus) select,[role=search]:has(button:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus) input:not([type=checkbox],[type=radio]),[role=search]:has(button:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus) select{border-color:transparent}[role=group]:has(input:not([type=submit],[type=button]):focus,select:focus),[role=search]:has(input:not([type=submit],[type=button]):focus,select:focus){--pico-group-box-shadow:var(--pico-group-box-shadow-focus-with-input)}[role=group]:has(input:not([type=submit],[type=button]):focus,select:focus) [role=button],[role=group]:has(input:not([type=submit],[type=button]):focus,select:focus) [type=button],[role=group]:has(input:not([type=submit],[type=button]):focus,select:focus) [type=submit],[role=group]:has(input:not([type=submit],[type=button]):focus,select:focus) button,[role=search]:has(input:not([type=submit],[type=button]):focus,select:focus) [role=button],[role=search]:has(input:not([type=submit],[type=button]):focus,select:focus) [type=button],[role=search]:has(input:not([type=submit],[type=button]):focus,select:focus) [type=submit],[role=search]:has(input:not([type=submit],[type=button]):focus,select:focus) button{--pico-button-box-shadow:0 0 0 var(--pico-border-width) var(--pico-primary-border);--pico-button-hover-box-shadow:0 0 0 var(--pico-border-width) var(--pico-primary-hover-border)}[role=group] [role=button]:focus,[role=group] [type=button]:focus,[role=group] [type=reset]:focus,[role=group] [type=submit]:focus,[role=group] button:focus,[role=search] [role=button]:focus,[role=search] [type=button]:focus,[role=search] [type=reset]:focus,[role=search] [type=submit]:focus,[role=search] button:focus{box-shadow:none}}[role=search]>:first-child{border-top-left-radius:5rem;border-bottom-left-radius:5rem}[role=search]>:last-child{border-top-right-radius:5rem;border-bottom-right-radius:5rem}[aria-busy=true]:not(input,select,textarea,html,form){white-space:nowrap}[aria-busy=true]:not(input,select,textarea,html,form)::before{display:inline-block;width:1em;height:1em;background-image:var(--pico-icon-loading);background-size:1em auto;background-repeat:no-repeat;content:"";vertical-align:-.125em}[aria-busy=true]:not(input,select,textarea,html,form):not(:empty)::before{margin-inline-end:calc(var(--pico-spacing) * .5)}[aria-busy=true]:not(input,select,textarea,html,form):empty{text-align:center}[role=button][aria-busy=true],[type=button][aria-busy=true],[type=reset][aria-busy=true],[type=submit][aria-busy=true],a[aria-busy=true],button[aria-busy=true]{pointer-events:none}:host,:root{--pico-scrollbar-width:0px}dialog{display:flex;z-index:999;position:fixed;top:0;right:0;bottom:0;left:0;align-items:center;justify-content:center;width:inherit;min-width:100%;height:inherit;min-height:100%;padding:0;border:0;-webkit-backdrop-filter:var(--pico-modal-overlay-backdrop-filter);backdrop-filter:var(--pico-modal-overlay-backdrop-filter);background-color:var(--pico-modal-overlay-background-color);color:var(--pico-color)}dialog>article{width:100%;max-height:calc(100vh - var(--pico-spacing) * 2);margin:var(--pico-spacing);overflow:auto}@media (min-width:576px){dialog>article{max-width:510px}}@media (min-width:768px){dialog>article{max-width:700px}}dialog>article>header>*{margin-bottom:0}dialog>article>header .close,dialog>article>header :is(a,button)[rel=prev]{margin:0;margin-left:var(--pico-spacing);padding:0;float:right}dialog>article>footer{text-align:right}dialog>article>footer [role=button],dialog>article>footer button{margin-bottom:0}dialog>article>footer [role=button]:not(:first-of-type),dialog>article>footer button:not(:first-of-type){margin-left:calc(var(--pico-spacing) * .5)}dialog>article .close,dialog>article :is(a,button)[rel=prev]{display:block;width:1rem;height:1rem;margin-top:calc(var(--pico-spacing) * -1);margin-bottom:var(--pico-spacing);margin-left:auto;border:none;background-image:var(--pico-icon-close);background-position:center;background-size:auto 1rem;background-repeat:no-repeat;background-color:transparent;opacity:.5;transition:opacity var(--pico-transition)}dialog>article .close:is([aria-current]:not([aria-current=false]),:hover,:active,:focus),dialog>article :is(a,button)[rel=prev]:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){opacity:1}dialog:not([open]),dialog[open=false]{display:none}.modal-is-open{padding-right:var(--pico-scrollbar-width,0);overflow:hidden;pointer-events:none;touch-action:none}.modal-is-open dialog{pointer-events:auto;touch-action:auto}:where(.modal-is-opening,.modal-is-closing) dialog,:where(.modal-is-opening,.modal-is-closing) dialog>article{animation-duration:.2s;animation-timing-function:ease-in-out;animation-fill-mode:both}:where(.modal-is-opening,.modal-is-closing) dialog{animation-duration:.8s;animation-name:modal-overlay}:where(.modal-is-opening,.modal-is-closing) dialog>article{animation-delay:.2s;animation-name:modal}.modal-is-closing dialog,.modal-is-closing dialog>article{animation-delay:0s;animation-direction:reverse}@keyframes modal-overlay{from{-webkit-backdrop-filter:none;backdrop-filter:none;background-color:transparent}}@keyframes modal{from{transform:translateY(-100%);opacity:0}}:where(nav li)::before{float:left;content:"​"}nav,nav ul{display:flex}nav{justify-content:space-between;overflow:visible}nav ol,nav ul{align-items:center;margin-bottom:0;padding:0;list-style:none}nav ol:first-of-type,nav ul:first-of-type{margin-left:calc(var(--pico-nav-element-spacing-horizontal) * -1)}nav ol:last-of-type,nav ul:last-of-type{margin-right:calc(var(--pico-nav-element-spacing-horizontal) * -1)}nav li{display:inline-block;margin:0;padding:var(--pico-nav-element-spacing-vertical) var(--pico-nav-element-spacing-horizontal)}nav li :where(a,[role=link]){display:inline-block;margin:calc(var(--pico-nav-link-spacing-vertical) * -1) calc(var(--pico-nav-link-spacing-horizontal) * -1);padding:var(--pico-nav-link-spacing-vertical) var(--pico-nav-link-spacing-horizontal);border-radius:var(--pico-border-radius)}nav li :where(a,[role=link]):not(:hover){text-decoration:none}nav li [role=button],nav li [type=button],nav li button,nav li input:not([type=checkbox],[type=radio],[type=range],[type=file]),nav li select{height:auto;margin-right:inherit;margin-bottom:0;margin-left:inherit;padding:calc(var(--pico-nav-link-spacing-vertical) - var(--pico-border-width) * 2) var(--pico-nav-link-spacing-horizontal)}nav[aria-label=breadcrumb]{align-items:center;justify-content:start}nav[aria-label=breadcrumb] ul li:not(:first-child){margin-inline-start:var(--pico-nav-link-spacing-horizontal)}nav[aria-label=breadcrumb] ul li a{margin:calc(var(--pico-nav-link-spacing-vertical) * -1) 0;margin-inline-start:calc(var(--pico-nav-link-spacing-horizontal) * -1)}nav[aria-label=breadcrumb] ul li:not(:last-child)::after{display:inline-block;position:absolute;width:calc(var(--pico-nav-link-spacing-horizontal) * 4);margin:0 calc(var(--pico-nav-link-spacing-horizontal) * -1);content:var(--pico-nav-breadcrumb-divider);color:var(--pico-muted-color);text-align:center;text-decoration:none;white-space:nowrap}nav[aria-label=breadcrumb] a[aria-current]:not([aria-current=false]){background-color:transparent;color:inherit;text-decoration:none;pointer-events:none}aside li,aside nav,aside ol,aside ul{display:block}aside li{padding:calc(var(--pico-nav-element-spacing-vertical) * .5) var(--pico-nav-element-spacing-horizontal)}aside li a{display:block}aside li [role=button]{margin:inherit}[dir=rtl] nav[aria-label=breadcrumb] ul li:not(:last-child) ::after{content:"\\"}progress{display:inline-block;vertical-align:baseline}progress{-webkit-appearance:none;-moz-appearance:none;display:inline-block;appearance:none;width:100%;height:.5rem;margin-bottom:calc(var(--pico-spacing) * .5);overflow:hidden;border:0;border-radius:var(--pico-border-radius);background-color:var(--pico-progress-background-color);color:var(--pico-progress-color)}progress::-webkit-progress-bar{border-radius:var(--pico-border-radius);background:0 0}progress[value]::-webkit-progress-value{background-color:var(--pico-progress-color);-webkit-transition:inline-size var(--pico-transition);transition:inline-size var(--pico-transition)}progress::-moz-progress-bar{background-color:var(--pico-progress-color)}@media (prefers-reduced-motion:no-preference){progress:indeterminate{background:var(--pico-progress-background-color) linear-gradient(to right,var(--pico-progress-color) 30%,var(--pico-progress-background-color) 30%) top left/150% 150% no-repeat;animation:progress-indeterminate 1s linear infinite}progress:indeterminate[value]::-webkit-progress-value{background-color:transparent}progress:indeterminate::-moz-progress-bar{background-color:transparent}}@media (prefers-reduced-motion:no-preference){[dir=rtl] progress:indeterminate{animation-direction:reverse}}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}[data-tooltip]{position:relative}[data-tooltip]:not(a,button,input,[role=button]){border-bottom:1px dotted;text-decoration:none;cursor:help}[data-tooltip]::after,[data-tooltip]::before,[data-tooltip][data-placement=top]::after,[data-tooltip][data-placement=top]::before{display:block;z-index:99;position:absolute;bottom:100%;left:50%;padding:.25rem .5rem;overflow:hidden;transform:translate(-50%,-.25rem);border-radius:var(--pico-border-radius);background:var(--pico-tooltip-background-color);content:attr(data-tooltip);color:var(--pico-tooltip-color);font-style:normal;font-weight:var(--pico-font-weight);font-size:.875rem;text-decoration:none;text-overflow:ellipsis;white-space:nowrap;opacity:0;pointer-events:none}[data-tooltip]::after,[data-tooltip][data-placement=top]::after{padding:0;transform:translate(-50%,0);border-top:.3rem solid;border-right:.3rem solid transparent;border-left:.3rem solid transparent;border-radius:0;background-color:transparent;content:"";color:var(--pico-tooltip-background-color)}[data-tooltip][data-placement=bottom]::after,[data-tooltip][data-placement=bottom]::before{top:100%;bottom:auto;transform:translate(-50%,.25rem)}[data-tooltip][data-placement=bottom]:after{transform:translate(-50%,-.3rem);border:.3rem solid transparent;border-bottom:.3rem solid}[data-tooltip][data-placement=left]::after,[data-tooltip][data-placement=left]::before{top:50%;right:100%;bottom:auto;left:auto;transform:translate(-.25rem,-50%)}[data-tooltip][data-placement=left]:after{transform:translate(.3rem,-50%);border:.3rem solid transparent;border-left:.3rem solid}[data-tooltip][data-placement=right]::after,[data-tooltip][data-placement=right]::before{top:50%;right:auto;bottom:auto;left:100%;transform:translate(.25rem,-50%)}[data-tooltip][data-placement=right]:after{transform:translate(-.3rem,-50%);border:.3rem solid transparent;border-right:.3rem solid}[data-tooltip]:focus::after,[data-tooltip]:focus::before,[data-tooltip]:hover::after,[data-tooltip]:hover::before{opacity:1}@media (hover:hover) and (pointer:fine){[data-tooltip]:focus::after,[data-tooltip]:focus::before,[data-tooltip]:hover::after,[data-tooltip]:hover::before{--pico-tooltip-slide-to:translate(-50%, -0.25rem);transform:translate(-50%,.75rem);animation-duration:.2s;animation-fill-mode:forwards;animation-name:tooltip-slide;opacity:0}[data-tooltip]:focus::after,[data-tooltip]:hover::after{--pico-tooltip-caret-slide-to:translate(-50%, 0rem);transform:translate(-50%,-.25rem);animation-name:tooltip-caret-slide}[data-tooltip][data-placement=bottom]:focus::after,[data-tooltip][data-placement=bottom]:focus::before,[data-tooltip][data-placement=bottom]:hover::after,[data-tooltip][data-placement=bottom]:hover::before{--pico-tooltip-slide-to:translate(-50%, 0.25rem);transform:translate(-50%,-.75rem);animation-name:tooltip-slide}[data-tooltip][data-placement=bottom]:focus::after,[data-tooltip][data-placement=bottom]:hover::after{--pico-tooltip-caret-slide-to:translate(-50%, -0.3rem);transform:translate(-50%,-.5rem);animation-name:tooltip-caret-slide}[data-tooltip][data-placement=left]:focus::after,[data-tooltip][data-placement=left]:focus::before,[data-tooltip][data-placement=left]:hover::after,[data-tooltip][data-placement=left]:hover::before{--pico-tooltip-slide-to:translate(-0.25rem, -50%);transform:translate(.75rem,-50%);animation-name:tooltip-slide}[data-tooltip][data-placement=left]:focus::after,[data-tooltip][data-placement=left]:hover::after{--pico-tooltip-caret-slide-to:translate(0.3rem, -50%);transform:translate(.05rem,-50%);animation-name:tooltip-caret-slide}[data-tooltip][data-placement=right]:focus::after,[data-tooltip][data-placement=right]:focus::before,[data-tooltip][data-placement=right]:hover::after,[data-tooltip][data-placement=right]:hover::before{--pico-tooltip-slide-to:translate(0.25rem, -50%);transform:translate(-.75rem,-50%);animation-name:tooltip-slide}[data-tooltip][data-placement=right]:focus::after,[data-tooltip][data-placement=right]:hover::after{--pico-tooltip-caret-slide-to:translate(-0.3rem, -50%);transform:translate(-.05rem,-50%);animation-name:tooltip-caret-slide}}@keyframes tooltip-slide{to{transform:var(--pico-tooltip-slide-to);opacity:1}}@keyframes tooltip-caret-slide{50%{opacity:0}to{transform:var(--pico-tooltip-caret-slide-to);opacity:1}}[aria-controls]{cursor:pointer}[aria-disabled=true],[disabled]{cursor:not-allowed}[aria-hidden=false][hidden]{display:initial}[aria-hidden=false][hidden]:not(:focus){clip:rect(0,0,0,0);position:absolute}[tabindex],a,area,button,input,label,select,summary,textarea{-ms-touch-action:manipulation}[dir=rtl]{direction:rtl}@media (prefers-reduced-motion:reduce){:not([aria-busy=true]),:not([aria-busy=true])::after,:not([aria-busy=true])::before{background-attachment:initial!important;animation-duration:1ms!important;animation-delay:-1ms!important;animation-iteration-count:1!important;scroll-behavior:auto!important;transition-delay:0s!important;transition-duration:0s!important}} \ No newline at end of file diff --git a/static/css/styles.css b/static/css/styles.css deleted file mode 100644 index 38af663..0000000 --- a/static/css/styles.css +++ /dev/null @@ -1,1400 +0,0 @@ -/* Header with logo */ -.header { - text-align: center; - margin-bottom: 2rem; -} - -.header h1 { - display: flex; - align-items: center; - justify-content: center; - gap: 1rem; - flex-wrap: wrap; -} - -.header-logo { - height: 1.2em; - width: auto; - vertical-align: middle; -} - -/* Status grid */ -.status-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 1rem; - margin-bottom: 2rem; -} - -.status-item { - padding: 1rem; - border-radius: var(--pico-border-radius); - text-align: center; - font-weight: bold; - background: var(--pico-card-background-color); - border: var(--pico-border-width) solid var(--pico-border-color); -} - -.status-connected { - background: var(--pico-primary-background); - color: var(--pico-primary-inverse); -} - -.status-disconnected { - background: var(--pico-secondary-background); - color: var(--pico-secondary-inverse); -} - -.status-reconnecting { - background: #ff9800; - color: white; - animation: pulse 2s infinite; -} - -@keyframes pulse { - 0% { - opacity: 1; - } - - 50% { - opacity: 0.7; - } - - 100% { - opacity: 1; - } -} - -.status-streaming { - background: var(--pico-primary-background); - color: var(--pico-primary-inverse); -} - -.status-idle { - background: var(--pico-muted-background-color); - color: var(--pico-muted-color); -} - -/* Configuration grid */ -.config-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); - gap: 1.5rem; - margin-bottom: 1.5rem; -} - -/* Form styling */ -.form-row { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 1rem; -} - -.controls { - display: flex; - flex-wrap: wrap; - gap: 0.5rem; - margin-top: 1rem; -} - -/* Variables table */ -.variables-table { - width: 100%; - margin-top: 1rem; -} - -/* Variable value cells styling */ -.variables-table td[id^="value-"] { - font-family: var(--pico-font-family-monospace); - font-weight: bold; - text-align: center; - min-width: 100px; -} - -/* Refresh button styling */ -#refresh-values-btn:disabled { - opacity: 0.6; - cursor: not-allowed; -} - -/* Diagnose button styling */ -#diagnose-btn:disabled { - opacity: 0.6; - cursor: not-allowed; -} - -/* Last refresh time styling */ -#last-refresh-time { - font-style: italic; - font-size: 0.85rem; -} - -/* Error cell tooltips */ -.variables-table td[id^="value-"]:hover { - position: relative; -} - -/* Variable value status colors */ -.value-success { - color: var(--pico-color-green-600) !important; -} - -.value-error { - color: var(--pico-color-red-500) !important; -} - -.value-warning { - color: var(--pico-color-amber-600) !important; -} - -.value-offline { - color: var(--pico-muted-color) !important; -} - -/* Alert styles */ -.alert { - padding: 1rem; - border-radius: var(--pico-border-radius); - margin: 1rem 0; - font-weight: bold; -} - -.alert-success { - background-color: var(--pico-primary-background); - color: var(--pico-primary-inverse); -} - -.alert-error { - background-color: var(--pico-color-red-400); - color: white; -} - -.alert-warning { - background-color: var(--pico-color-amber-100); - color: var(--pico-color-amber-800); - border: 1px solid var(--pico-color-amber-300); -} - -.alert-info { - background-color: var(--pico-color-blue-100); - color: var(--pico-color-blue-800); - border: 1px solid var(--pico-color-blue-300); -} - -/* Dataset controls */ -.dataset-controls { - background: var(--pico-card-background-color); - border: var(--pico-border-width) solid var(--pico-border-color); - padding: 1.5rem; - border-radius: var(--pico-border-radius); - margin-bottom: 1.5rem; -} - -/* Info section */ -.info-section { - background: var(--pico-muted-background-color); - border: var(--pico-border-width) solid var(--pico-muted-border-color); - border-radius: var(--pico-border-radius); - padding: 1.5rem; - margin-bottom: 1.5rem; -} - -.info-section p { - margin: 0.5rem 0; -} - -/* Log container */ -.log-container { - max-height: 400px; - overflow-y: auto; - background: var(--pico-background-color); - border: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - padding: 1rem; - font-family: var(--pico-font-family-monospace); -} - -.log-entry { - display: flex; - flex-direction: column; - margin-bottom: 1rem; - padding: 0.75rem; - border-radius: var(--pico-border-radius); - font-size: 0.875rem; - border-left: 3px solid transparent; -} - -.log-entry.log-info { - background: var(--pico-card-background-color); - border-left-color: var(--pico-primary); -} - -.log-entry.log-warning { - background: var(--pico-color-amber-50); - border-left-color: var(--pico-color-amber-500); -} - -.log-entry.log-error { - background: var(--pico-color-red-50); - border-left-color: var(--pico-color-red-500); -} - -.log-header { - display: flex; - justify-content: space-between; - align-items: center; - font-weight: bold; - margin-bottom: 0.25rem; -} - -.log-timestamp { - font-size: 0.75rem; - opacity: 0.7; -} - -.log-message { - margin-bottom: 0.25rem; - word-wrap: break-word; -} - -.log-details { - font-size: 0.75rem; - opacity: 0.8; - background: rgba(0, 0, 0, 0.1); - padding: 0.5rem; - border-radius: var(--pico-border-radius); - margin-top: 0.25rem; - white-space: pre-wrap; -} - -.log-controls { - display: flex; - gap: 1rem; - margin-bottom: 1rem; - flex-wrap: wrap; - align-items: center; -} - -.log-stats { - background: var(--pico-muted-background-color); - border: var(--pico-border-width) solid var(--pico-muted-border-color); - border-radius: var(--pico-border-radius); - padding: 0.5rem 1rem; - font-size: 0.875rem; -} - -/* Utility classes */ -.status-active { - color: var(--pico-primary); - font-weight: bold; -} - -.status-inactive { - color: var(--pico-muted-color); -} - -.status-error { - color: var(--pico-color-red-500); -} - -/* Modal improvements */ -.modal { - position: fixed; - z-index: 1000; - left: 0; - top: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.5); - backdrop-filter: blur(5px); -} - -.modal article { - margin: 5% auto; - width: 90%; - max-width: 600px; -} - -.modal header { - display: flex; - justify-content: space-between; - align-items: center; -} - -.close { - font-size: 1.5rem; - font-weight: bold; - cursor: pointer; - border: none; - background: none; - color: inherit; -} - -.close:hover { - opacity: 0.7; -} - -/* Mobile responsive */ -@media (max-width: 768px) { - .header h1 { - flex-direction: column; - gap: 0.5rem; - } - - .header-logo { - height: 1.2em; - } - - .config-grid { - grid-template-columns: 1fr; - } - - .form-row { - grid-template-columns: 1fr; - } - - .controls { - flex-direction: column; - } - - .status-grid { - grid-template-columns: 1fr; - } -} - -/* Theme selector */ -.theme-selector { - position: fixed; - top: 1rem; - right: 1rem; - z-index: 1000; - background: var(--pico-card-background-color); - border: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - padding: 0.5rem; - display: flex; - gap: 0.5rem; - align-items: center; - box-shadow: var(--pico-box-shadow); -} - -.theme-selector button { - padding: 0.25rem 0.5rem; - font-size: 0.75rem; - border-radius: var(--pico-border-radius); - cursor: pointer; - transition: all 0.2s ease; -} - -.theme-selector button.active { - background: var(--pico-primary-background); - color: var(--pico-primary-inverse); -} - -.theme-selector button:not(.active) { - background: var(--pico-muted-background-color); - color: var(--pico-muted-color); -} - -.theme-selector button:hover:not(.active) { - background: var(--pico-primary-hover); - color: var(--pico-primary-inverse); -} - -/* Font size reduction - 15% smaller (more balanced) */ -html { - font-size: 85%; - /* 15% reduction from 100% - more balanced */ -} - -/* Adjust specific elements that might need fine-tuning */ -.header h1 { - font-size: 2.2rem; - /* Adjusted for smaller base font */ -} - -.header p { - font-size: 1.1rem; -} - -.status-item { - font-size: 1rem; -} - -.log-entry { - font-size: 0.9rem; - /* Adjusted for smaller base font */ -} - -.log-timestamp { - font-size: 0.8rem; - /* Adjusted for smaller base font */ -} - -.log-details { - font-size: 0.8rem; - /* Adjusted for smaller base font */ -} - -.log-stats { - font-size: 0.9rem; - /* Adjusted for smaller base font */ -} - -/* Ensure buttons and inputs remain readable */ -button, -input, -select, -textarea { - font-size: 1rem; -} - -/* Table adjustments */ -.variables-table th, -.variables-table td { - font-size: 0.95rem; -} - -/* Modal adjustments */ -.modal article { - font-size: 1rem; -} - -.modal h3 { - font-size: 1.4rem; -} - -/* CSV Configuration styles */ -.csv-config-display { - background: var(--pico-card-background-color); - border: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - padding: 1.5rem; - margin-bottom: 1rem; -} - -.config-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); - gap: 1rem; -} - -.config-item { - display: flex; - justify-content: space-between; - align-items: center; - padding: 0.5rem 0; - border-bottom: 1px solid var(--pico-muted-border-color); -} - -.config-item:last-child { - border-bottom: none; -} - -.config-item span { - font-weight: normal; - color: var(--pico-primary); -} - -.directory-stats { - padding: 1rem; - background: var(--pico-muted-background-color); - border-radius: var(--pico-border-radius); - margin-top: 0.5rem; -} - -.directory-stats .stat-item { - display: flex; - justify-content: space-between; - margin: 0.5rem 0; -} - -.day-folder-item { - background: var(--pico-card-background-color); - border: 1px solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - padding: 0.75rem; - margin: 0.5rem 0; - display: flex; - justify-content: space-between; - align-items: center; -} - -.cleanup-status { - padding: 0.5rem; - border-radius: var(--pico-border-radius); - margin: 0.5rem 0; - font-weight: bold; -} - -.cleanup-success { - background-color: var(--pico-color-green-100); - color: var(--pico-color-green-800); - border: 1px solid var(--pico-color-green-300); -} - -.cleanup-error { - background-color: var(--pico-color-red-100); - color: var(--pico-color-red-800); - border: 1px solid var(--pico-color-red-300); -} - -/* 📈 PLOT SYSTEM STYLES */ -.plot-session { - background: var(--pico-card-background-color); - border: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - margin-bottom: 1rem; - /* Evitar recortar etiquetas inferiores del eje del chart */ - overflow: visible; -} - -.plot-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 0.75rem 1rem; - background: var(--pico-muted-background-color); - border-bottom: var(--pico-border-width) solid var(--pico-border-color); -} - -.plot-header h4 { - margin: 0; - color: var(--pico-h4-color); - font-size: 1.1rem; -} - -.plot-controls { - display: flex; - gap: 0.5rem; - flex-wrap: wrap; -} - -.plot-controls .btn { - padding: 0.25rem 0.5rem; - font-size: 0.8rem; - min-width: auto; -} - -.refresh-rate-control { - display: flex; - align-items: center; - gap: 0.25rem; - margin-left: 0.5rem; -} - -.refresh-rate-control label { - font-size: 0.9rem; - margin: 0; - cursor: pointer; -} - -.refresh-rate-input { - padding: 0.15rem 0.3rem; - font-size: 0.75rem; - width: 80px; - height: auto; - border-radius: var(--pico-border-radius); - border: 1px solid var(--pico-border-color); - background: var(--pico-background-color); - color: var(--pico-color); - text-align: center; -} - -.refresh-rate-unit { - font-size: 0.7rem; - color: var(--pico-muted-color); - margin-left: 0.2rem; -} - -.plot-info { - padding: 0.5rem 1rem; - background: var(--pico-card-background-color); - border-bottom: var(--pico-border-width) solid var(--pico-border-color); - font-size: 0.85rem; - color: var(--pico-muted-color); -} - -.plot-stats { - display: flex; - gap: 1rem; - flex-wrap: wrap; -} - -.plot-canvas { - padding: 1rem; - height: 400px; - position: relative; - background: var(--pico-card-background-color); - border-radius: var(--pico-border-radius); - border: var(--pico-border-width) solid var(--pico-border-color); -} - -.plot-canvas canvas { - width: 100% !important; - /* Permitir a Chart.js ajustar el alto con precisión */ - height: 100%; - display: block; - max-height: 100%; -} - -/* Modal de creación de plots */ -.modal { - display: none; - position: fixed; - z-index: 1000; - left: 0; - top: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.5); -} - -.modal-content { - background-color: var(--pico-card-background-color); - margin: 5% auto; - padding: 2rem; - border: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - width: 90%; - max-width: 600px; - max-height: 80vh; - overflow-y: auto; -} - -.modal-content h4 { - margin-top: 0; - color: var(--pico-h4-color); -} - -.form-group { - margin-bottom: 1rem; -} - -.form-group label { - display: block; - margin-bottom: 0.5rem; - font-weight: bold; - color: var(--pico-form-element-label-color); -} - -.form-group input, -.form-group select { - width: 100%; - padding: 0.5rem; - border: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - background: var(--pico-form-element-background-color); - color: var(--pico-form-element-color); -} - -.form-group input:focus, -.form-group select:focus { - border-color: var(--pico-primary); - outline: none; -} - -.variable-checkbox { - margin-bottom: 0.5rem; -} - -.variable-checkbox label { - display: flex; - align-items: center; - gap: 0.5rem; - font-weight: normal; - cursor: pointer; -} - -.variable-checkbox input[type="checkbox"] { - width: auto; - margin: 0; -} - -.range-inputs { - display: flex; - align-items: center; - gap: 0.5rem; -} - -.range-inputs input { - flex: 1; -} - -.range-inputs span { - color: var(--pico-muted-color); - font-weight: bold; -} - -.form-actions { - display: flex; - gap: 1rem; - justify-content: flex-end; - margin-top: 2rem; - padding-top: 1rem; - border-top: var(--pico-border-width) solid var(--pico-border-color); -} - -.form-actions .btn { - min-width: 100px; -} - -#trigger-config { - padding: 1rem; - background: var(--pico-muted-background-color); - border: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - margin-top: 0.5rem; -} - -#trigger-config .form-group { - margin-bottom: 0.5rem; -} - -#trigger-config .form-group:last-child { - margin-bottom: 0; -} - -/* Responsive design para plots */ -@media (max-width: 768px) { - .plot-header { - flex-direction: column; - align-items: flex-start; - gap: 0.5rem; - } - - .plot-controls { - width: 100%; - justify-content: flex-start; - } - - .plot-stats { - flex-direction: column; - gap: 0.25rem; - } - - .modal-content { - margin: 10% auto; - width: 95%; - padding: 1rem; - } - - .form-actions { - flex-direction: column; - } - - .range-inputs { - flex-direction: column; - align-items: stretch; - } -} - -/* TAB SYSTEM STYLES */ -.tabs { - display: flex; - border-bottom: var(--pico-border-width) solid var(--pico-border-color); - margin-bottom: 1.5rem; - background: var(--pico-card-background-color); - border-radius: var(--pico-border-radius) var(--pico-border-radius) 0 0; - overflow-x: auto; -} - -.tab-btn { - padding: 1rem 1.5rem; - border: none; - background: none; - color: var(--pico-muted-color); - cursor: pointer; - font-weight: 500; - border-bottom: 3px solid transparent; - transition: all 0.2s ease; - white-space: nowrap; - flex: 1; - text-align: center; - min-width: 0; - max-width: none; -} - -.tab-btn:hover { - color: var(--pico-primary); - background: var(--pico-muted-background-color); -} - -.tab-btn.active { - color: var(--pico-primary); - border-bottom-color: var(--pico-primary); - background: var(--pico-card-background-color); -} - -/* Plot tabs específicos */ -.tab-btn.plot-tab { - position: relative; - padding-right: 2.5rem; -} - -.tab-close { - position: absolute; - right: 0.5rem; - top: 50%; - transform: translateY(-50%); - background: none; - border: none; - color: var(--pico-muted-color); - cursor: pointer; - font-size: 1.2rem; - line-height: 1; - padding: 0.25rem; - border-radius: 50%; - transition: all 0.2s ease; -} - -.tab-close:hover { - background: var(--pico-muted-background-color); - color: var(--pico-color-red-500); -} - -/* SUB-TABS STYLES */ -.sub-tabs { - display: flex; - border-bottom: var(--pico-border-width) solid var(--pico-border-color); - margin-bottom: 1rem; - background: var(--pico-muted-background-color); - border-radius: var(--pico-border-radius); - overflow-x: auto; -} - -.sub-tab-btn { - padding: 0.75rem 1rem; - border: none; - background: none; - color: var(--pico-muted-color); - cursor: pointer; - font-weight: 500; - border-bottom: 2px solid transparent; - transition: all 0.2s ease; - white-space: nowrap; - flex: 1; - text-align: center; - min-width: 0; - font-size: 0.9rem; -} - -.sub-tab-btn:hover { - color: var(--pico-primary); - background: var(--pico-card-background-color); -} - -.sub-tab-btn.active { - color: var(--pico-primary); - border-bottom-color: var(--pico-primary); - background: var(--pico-card-background-color); -} - -.sub-tab-btn.plot-sub-tab { - position: relative; - padding-right: 2rem; -} - -.sub-tab-close { - position: absolute; - right: 0.25rem; - top: 50%; - transform: translateY(-50%); - background: none; - border: none; - color: var(--pico-muted-color); - cursor: pointer; - font-size: 1rem; - line-height: 1; - padding: 0.2rem; - border-radius: 50%; - transition: all 0.2s ease; -} - -.sub-tab-close:hover { - background: var(--pico-muted-background-color); - color: var(--pico-color-red-500); -} - -.sub-tab-content { - display: none; -} - -.sub-tab-content.active { - display: block; -} - -.tab-content { - display: none; -} - -.tab-content.active { - display: block; -} - -/* COLLAPSIBLE PLOT FORM STYLES */ -.collapsible-section { - margin-bottom: 1.5rem; - border: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - overflow: hidden; - transition: all 0.3s ease; -} - -.plot-form-article { - margin: 0; - background: var(--pico-card-background-color); -} - -.plot-form-article header { - background: var(--pico-primary-background); - color: var(--pico-primary-inverse); - padding: 1rem 1.5rem; - margin: 0; -} - -.close-btn { - background: none; - border: none; - color: inherit; - font-size: 1.5rem; - cursor: pointer; - padding: 0.25rem; - border-radius: 50%; - transition: all 0.2s ease; -} - -.close-btn:hover { - background: rgba(255, 255, 255, 0.2); -} - -.variables-selection { - border: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - padding: 1rem; - background: var(--pico-muted-background-color); -} - -.selected-variables { - margin-bottom: 1rem; - min-height: 2rem; - padding: 0.5rem; - border: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - background: var(--pico-card-background-color); -} - -.selected-variables .no-variables { - color: var(--pico-muted-color); - font-style: italic; - margin: 0; -} - -.variable-chip { - display: inline-flex; - align-items: center; - gap: 0.5rem; - padding: 0.25rem 0.75rem; - margin: 0.25rem; - background: var(--pico-primary-background); - color: var(--pico-primary-inverse); - border-radius: var(--pico-border-radius); - font-size: 0.875rem; - font-weight: 500; -} - -.variable-chip .color-indicator { - width: 12px; - height: 12px; - border-radius: 50%; - border: 1px solid rgba(255, 255, 255, 0.3); -} - -.variable-chip .remove-variable { - background: none; - border: none; - color: inherit; - cursor: pointer; - font-size: 1.1rem; - padding: 0; - margin-left: 0.25rem; - opacity: 0.8; - transition: opacity 0.2s ease; -} - -.variable-chip .remove-variable:hover { - opacity: 1; -} - -/* VARIABLE SELECTION MODAL STYLES */ -.variable-modal { - max-width: 900px; - width: 95%; - max-height: 85vh; - display: flex; - flex-direction: column; -} - -.modal-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 1.5rem; - background: var(--pico-primary-background); - color: var(--pico-primary-inverse); - margin: 0; - border-radius: var(--pico-border-radius) var(--pico-border-radius) 0 0; -} - -.modal-header h3 { - margin: 0; - color: inherit; -} - -.modal-body { - flex: 1; - padding: 1.5rem; - overflow-y: auto; -} - -.modal-footer { - padding: 1rem 1.5rem; - background: var(--pico-muted-background-color); - border-top: var(--pico-border-width) solid var(--pico-border-color); - display: flex; - justify-content: flex-end; - gap: 1rem; -} - -.variable-selection-container { - display: grid; - grid-template-columns: 200px 1fr; - gap: 1.5rem; - margin-bottom: 1.5rem; -} - -.datasets-sidebar { - border: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - padding: 1rem; - background: var(--pico-muted-background-color); - height: fit-content; -} - -.datasets-sidebar h4 { - margin: 0 0 1rem 0; - color: var(--pico-h4-color); - font-size: 1rem; -} - -.datasets-list { - display: flex; - flex-direction: column; - gap: 0.5rem; -} - -.dataset-item { - padding: 0.75rem; - border: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - background: var(--pico-card-background-color); - cursor: pointer; - transition: all 0.2s ease; - font-size: 0.875rem; -} - -.dataset-item:hover { - background: var(--pico-primary-hover); - color: var(--pico-primary-inverse); -} - -.dataset-item.active { - background: var(--pico-primary-background); - color: var(--pico-primary-inverse); -} - -.dataset-item .dataset-name { - font-weight: bold; - display: block; -} - -.dataset-item .dataset-info { - font-size: 0.75rem; - opacity: 0.8; - margin-top: 0.25rem; -} - -.variables-main { - border: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - background: var(--pico-card-background-color); -} - -.variables-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 1rem; - background: var(--pico-muted-background-color); - border-bottom: var(--pico-border-width) solid var(--pico-border-color); -} - -.variables-header h4 { - margin: 0; - color: var(--pico-h4-color); - font-size: 1rem; -} - -.selection-controls { - display: flex; - gap: 0.5rem; -} - -.selection-controls .btn { - padding: 0.25rem 0.75rem; - font-size: 0.75rem; -} - -.variables-list { - padding: 1rem; - max-height: 300px; - overflow-y: auto; -} - -.variable-item { - display: flex; - align-items: center; - justify-content: space-between; - padding: 0.75rem; - margin-bottom: 0.5rem; - border: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - background: var(--pico-muted-background-color); - transition: all 0.2s ease; -} - -.variable-item:last-child { - margin-bottom: 0; -} - -.variable-item.selected { - background: var(--pico-primary-background); - color: var(--pico-primary-inverse); - border-color: var(--pico-primary); -} - -.variable-info { - flex: 1; -} - -.variable-name { - font-weight: bold; - margin-bottom: 0.25rem; -} - -.variable-details { - font-size: 0.75rem; - opacity: 0.8; -} - -.variable-controls { - display: flex; - align-items: center; - gap: 0.75rem; -} - -.variable-checkbox { - transform: scale(1.2); -} - -.color-selector { - width: 40px; - height: 30px; - border: none; - border-radius: var(--pico-border-radius); - cursor: pointer; - transition: all 0.2s ease; -} - -.color-selector:hover { - transform: scale(1.1); -} - -.color-selector:disabled { - opacity: 0.5; - cursor: not-allowed; - transform: none; -} - -.selected-summary { - border: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - padding: 1rem; - background: var(--pico-muted-background-color); -} - -.selected-summary h4 { - margin: 0 0 1rem 0; - color: var(--pico-h4-color); - font-size: 1rem; -} - -.selected-summary-list { - display: flex; - flex-wrap: wrap; - gap: 0.5rem; - min-height: 2rem; -} - -.selected-summary-item { - display: flex; - align-items: center; - gap: 0.5rem; - padding: 0.5rem 0.75rem; - background: var(--pico-card-background-color); - border: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - font-size: 0.875rem; -} - -.selected-summary-item .color-indicator { - width: 16px; - height: 16px; - border-radius: 50%; - border: 1px solid var(--pico-border-color); -} - -.no-dataset-message { - text-align: center; - color: var(--pico-muted-color); - font-style: italic; - padding: 2rem; - margin: 0; -} - -/* Responsive design para variable modal */ -@media (max-width: 768px) { - .variable-modal { - width: 98%; - max-height: 90vh; - } - - .variable-selection-container { - grid-template-columns: 1fr; - gap: 1rem; - } - - .datasets-sidebar { - max-height: 150px; - overflow-y: auto; - } - - .variables-header { - flex-direction: column; - align-items: flex-start; - gap: 0.5rem; - } - - .selection-controls { - width: 100%; - justify-content: flex-end; - } - - .variable-item { - flex-direction: column; - align-items: flex-start; - gap: 0.5rem; - } - - .variable-controls { - width: 100%; - justify-content: flex-end; - } - - .modal-footer { - flex-direction: column; - gap: 0.5rem; - } -} - -/* Responsive tabs */ -@media (max-width: 768px) { - .tabs { - flex-direction: column; - border-bottom: none; - border-right: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius) 0 0 var(--pico-border-radius); - margin-bottom: 1rem; - } - - .tab-btn { - border-bottom: none; - border-right: 3px solid transparent; - text-align: left; - min-width: auto; - } - - .tab-btn.active { - border-right-color: var(--pico-primary); - } -} - -/* ============================= - Config Editor (JSON Schema) UI compact layout - Works for both JSONForm (#jsonform-form) and fallback renderer (.config-editor-form) - ============================= */ -/* Grid layout for JSONForm-generated form */ -#jsonform-form { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); - gap: 1rem; -} - -#jsonform-form .control-group { - margin: 0; -} - -#jsonform-form .form-actions { - grid-column: 1 / -1; -} - -/* Grid layout for fallback renderer */ -.config-editor-form { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); - gap: 1rem; -} - -.config-editor-form>.form-group { - margin: 0; -} - -/* Group inner properties in a compact grid */ -.config-editor-form .object-group { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); - gap: 0.75rem; - padding: 0.75rem; - border: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - background: var(--pico-muted-background-color); -} - -.config-editor-form .object-group>.form-group { - margin: 0; -} - -/* Make number inputs a bit tighter to save space */ -.config-editor-form input[type="number"] { - max-width: 100%; -} - -/* Responsive tweak for very small screens */ -@media (max-width: 480px) { - - #jsonform-form, - .config-editor-form, - .config-editor-form .object-group { - grid-template-columns: 1fr; - } -} \ No newline at end of file diff --git a/static/icons/favicon.ico b/static/icons/favicon.ico deleted file mode 100644 index 3f5ce654de5fb8925ca85753e7d5170e22fd0089..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15983 zcmeIZ_g7Qj6E1u}kS5>DQqEzXGUZokTfDq{@Dk8miLFv8sYN3Q6Na$U9 z4?UDX@*cnMFZW-#YuzkZfh1?2oxS(WJoC&m5xUx{l;lk0002;`t3A~R07CFjLV)xp z_;m7S>=Jw;@_wRjND4jzN$o#@|C7B^GxY`ls$c(p36ceZQ$Yf8gA?5 z0KnmJVVJv%x1H@P2VoB{r%d!?CIDau)SoIDzRB8~ho`ZOr|%ywmTZgW=_%%YiX$Zt zRb;p2ROGaa>kdWSB#9EMzF9STvxpL2WxDS_iSZBGJ7s#M_~yG+rmGhf_a|O2eMOlK zNeR}{?KuTAnkT5ilh?*R(6c~=;XKvB=i@R}Y3Rx(?d;=wWZ>lf-{b$<6?p&i7ptQ2 zgtgxqV8F}5-Y27|B#P1qT9v+z9<#65V}=I;fZB{}G)zK2Ghgcw=_6^>T!ouko$9>5 zi|X8WA8s+S?m~gem#0B^2A9DR6)sX#Y4PAs z#g^v_IPAqYpxJ=X_tgnsY9|(oUmP|nb>Xkq&3vuuz#-RCW2xT#H5Xrhp?>W=E&>2P zxxdY@F3dy<_X1avbgMm@52o5 z?A-wVXbMX8UR`%HO1R%@ThgTs6-u=buTRsMXWxnvoh1fxQ`@A3EEo1aGEcl8w|Pm{ z{ShgGPiw%y6I9-ha{~ac7>a)}ZJ6?l*vd%UOj%6w;KxW2T)KFAm73R+PoRbErt`RT zL0;jnLu@KLWs3{HmeK$LYsoUV7^7H@35b+q{*1l7jYd-k(4R(fAW^EYY;fKXD_ zMO4$i5N5l~VC{>pFJbj|K`OfD!;kODJ;VU;024tONsrH;3qxuE6IsER^jh*dJ_-Pk zFgR0H(Y0GUoaFto2?q+N6re3Kg}a~!(rJZ%sA5lsOAPZ4T~QYU-ZX!n@uI_PL& z^rPOvnu*qVpI;o{ve6XHn*?ljFXDWcz64Kgk%oF^<)nHak^n%&SRd2z=umKTACb*C zU(xdhrS9?&0N9?D*{Nz?9+bPbX(H;@=#?INIMPQ1el>a`yz|*Xe@GL!J@v2+a~Ga* zZhio`U8Bw{+;z3m7VNFV0ay+(fuHs1oe7yaHD}WRjC6A35o!i5^#6g|^R|S!vU&Me zcGuxoBKB&GOG7j`jj?wP7!hBbzbaw%;h+QwlMAwx?G`wVsD_Oy+)*ml9q*+|sLOjt zUck=3K2;PPhFNIK0)4e-y{iVe-nO`-^B4efxyw83u^~+AW`5fx%38MtQw{aj6f_8K z0J`*nc6)!{c3toFqDVsg`QmzCqygT)eq4KzwL8;5Zd8@>xC(P9 z$xt^eFCMNVcs*bAY$%~8p}{j8`rqsVcbje4I|t;QaudBKe9XtSR`Gy6t|Fhf=lEcK zS&l>v^`Hz*1>__3iGg)l&c#$OKhxpa!+-{6sR8$hC5d9~DO7RS;KxMmnX)*I3S)Zn z8}M)$pd(65oR+&kEBK@2>oni%%|Uw!jRO6e_l>F(1C`cQHouvOHB*R2ivo8o?a@Kj9_1eaH0C@BVF z)tE@V{81ffq#&fr3DNEeK*!HFi=kgKB`}IVMSl$+V1;O-JFx?|81=F2?-aiut&y(} z#-A;;k*^OW|6X%uQynDSVyN?hgD-<0-RhJmHzf0 zGB0Ww&My?f@OO&1Zy(4Pf>jsFs!lTW*zE=j9M?{^6eK2sJ2F110ZchE_(W7qUy6^b z95(Zzi8I{K=xTLh3ib!F8hl=4HjdHvO<|oqXN@3wdx!<)ig^v$M zk@D@8w4vEVk@RKf2g*lQeg$v~B5oFL?=AX)V9d-PvhGJcB=>Ze&~3|9dBV~^!v!9m zniRyyYB*MPEK~8^`Tp%0Kkj1q$U0cP(Ig6zrOW8zAtg|x6#%-EGT0}gT+j4~#Qb4P zR34d3bOPW?FKCwkL#U}g2Om85^{La8XnGaCA?bHYkcNjEU|(@0H5do3v8QT+2|BGt zBgZ#mj*PZ6r#V%(F;5wHJY04#l~=nn(Zi~3VkKHusAbLxTXZ(NPw-NAt9}rUv)Zc%GeSrloxuj8G-+AE%d$mCeIm;=1=Y?g6BPvd%F&Iw!Jvu|vN zr9ZNief=bM*xR5o%blBig4J#{ilt!euy5Z)VS#t>kVj25Fu!l3Kw^vZoBF+#R`DS0 zK*-*Bv$8r79L4$2@|3GU=dqr4*sasl-wZO?-Mo_swqyzWS6kk>!Kt(x`U%ZmcJTD5 zlaa*bmJh2EAj-Y3mBnlD z-SX>LE(g%|N+rf{yPsoi_>1KtU0+N(zxVkPUG|K*3c#8C+1dehWE~)cR&ySzZ{!a+ z+(^1u~PZ&0W(tO7HTVcI-+AZ(PC=>pDdiY%3}xG!VEh;f%wE#I@lGJi`qt z@6d@&j9 zdC$Wtll3c#B>7Hl-$``ggaRhFMNA|Lox~@;h=ZgPTqh&!m#K~UPWZ8GJ z2a7}f9iqf@E~4flXjfwL1E_xDpLvk3_(y zP?)nmrc?wWI=G;F)!?#Bb)RLrd@kf%p?22jp?xi5`u4#0Tng@Nqsyq(Xu>!E_|Nkr zrNR30a1ZOvCwBxbnc$@3JEN*-1zh4gH-|ujtt1Phu&2U7yPlv4^sx4Z|GPwk)z7}< z6Z!U+-y3v;Ic#tSsMs|Yd7*&M&((o(@bvklofr$maGX{DT#i{*Rs%vmIS5}GeoH0% z`WUxUrTK*(&Oj#tTGYkRBGOTV_Bu*zz!AJev*f)N6*mF!!140T`10y_h3dw5wb25t z&M($yYZ;kqSMv(IQ?Qf}{=@{&H)w0kAp%4xr#c}prs%(2<3-KD3TG&eI~x!69$gau8=vsT}AK?pCG~QbDku?;I0^5P7wFM&puaxV*XHjK;5GBWPi?w zlsCFZL*B81i^JsA7nR(G$9aao6~B|?XZ8W&9t(;3oK|C3Fe=|Udp5cyNgVRR{@8E& z$$hk_ugxTVZtq-)!J3E#1Ds(qYNO;JT%lO{c)FRR1avfmYa#dBkWJBIUIvKt&YVuBQscOekjll#F3t@B>*V>LI|A9-NY2d*Z;v~JUs&O! zY(Gc=UAw{-Q*=JZYRCDLxvz=|dz*3iOP3?u)kZ5@y4YwTl4>!CmLH8v{2p+c+^l?l zn+D+2ueOf>gO;Y-o*bkmL;?0o9`*M-%Bn(t6rDU26}6g#^JP)(6?@nv0iGdFve0~+ z3oqhGdc;Kak1vOT(=k-jr#p0lmi!@y5BNx(CqnFRQYOgkt+Y{IYwGhSmm=Fj+C1}i zOQA=`RS7c?d&rCf1#$X|tjC^bU z`M>GK;AdP0uGc3M_KhcXH6sT{RFNz`QM-RZnBo`}?-_+EdHoxmkadGvkdCMXvuM&s z4xg)B&WqLX)kzgR`6T9rTjrnE7Czc~edL6jwb1_F?sv2?q$djh@@@@je|+(I<{!9o zT2*VY>qE$FvbSK-0zK^~HE$M!YA{_qoPb)^)n3yFZ(CQF#GotB={~m9-q=)`V?o&Y z!8}#n#K5JIpMr>iq(;%GM!~lrm$X9e7LuqG1rPW9YtCc^c+BV6$24$;=oLG*MAq63 zPN`%Kz7krzn`(Hwv32~}4+`@2-xO|~z3y|oAuB2u9vxij;zB{m8Pa;_RfHF{Z1FlG z6hC1Od7(}NZr11DYxa|slwC{k#pG-`bH|z2u*;I{yDQaiq4%xh_ObE%@#q+~MHkvo z8+3cK@=KPW%l@oZY(m%GH*H6OrQs0kg_T%j$O`533$Y`d>#Px-lYH6opp#~Rch;+K z`aEAu5=#Spj~&-Mu5$XZB~Op682^))F3#Zw3>HyDWg`BS5w1l%ffDf9;aHtqcUfDrpHHM2EyLj*& zXLkh6?<&<7KTCg(U1t^#xl-mM+x({BQsKoq!bLE2@180kXRER&ptTtodyzkPjisP3 zeRKR9F{6t(HVnit(pkT;Dp`p!STCxEGr&4(5d?dKA^w&j?42b# z!d(a1f3;Hye^i^?aJ!Bwx=K_&Xhv9PvzPyV0|P8YRWFP01IQZ zb)cRS54NoD(ecA%{mYZl>~RZ$1{waYP2rbT=VYM>BLxNG z4js6+aU*?GX@Sj}#u^U00mAO_`o-euCrY^X7D^fY)VS08a&{jn6OTJWwoaBkp6_Y4 zq3{0u``!7nS1MWdVs*xP?2m+ld@b44LF(I#_09E?TqR02;k`EN)bRYdnT6Sl<3(e1 zAAaN&Jzm8aP8OOjs_+Yt^VFV7+;zfI`?% z=2=B{npDsf?Z)4*@sr7rpK%`WnW3aFk5+4`A}{|d#`iKGeR=Z$C9lhj z&;p|3FP5RT$zz8)m{aS7B>Cg#G9+v)a0Y|{QWc2d@V-oUV9n4j;_!R?pQ{O*f96Zw zaBbe5YF=7kcI4|78?H%M;K~*rp#==8>LjQ{e}+VDGUOE^g++GIb;czQgs)J2`TudW z$_;vEt6>P@@@@8J)MlQAF-lk0AW9#sQM&9`R*|<Wp&BRkyd=r{k5Ym7^yU{a6ah&R-1VskYsk_QFxb** zt$fm2<|oMT9x7>#?w&AC*^Xl2gD*e5sOq>2=RK5A89G=3NfYX(`I;5`A=$E72g6z5y?QwOqc zu|BKeSLa^{vF6-+V1>{CdSen*b`5+alvt?@9EPL~SgX1gT*BYOQ)G5r*Md)x+jwiq zhX@uKQ`vO}*nTAswk1YDYGuNmmTuTUG}UKp2&8;or0p_;&}+YkepT-J^%2rLHQ=o3 zB{wkRv(t?A(2dXl2BufPu{K1#A&O3p5YCuj$PMCg-7rH3tg@oer1QsO-KZ8F|f0~*5%bQb#b_1 zetcbBmi$E)>PG{pCqNusm33lHto-@Vx7iW(C(!k|<0M(HTCYhf4)gV)xQFAvH=+6E z$!Ax`ho`Y>TuwB9;}ey$NjHcQgX?X}+Okgy6Mm=`3pAP};b+P~Jf1C`r&4mkhSx(+ zZF94S4|&>$Q}JP6x%}ubpmrSAcGQ2b%gIe3nixac(9vDhy=WN+u;9RN@ zzSSg(sI-*Mf#5wCFU+h6T324Q8p;jDt=rlv5FRY}LB=p~f(SL>@>HPgfw>M*u&yIon){!Um{TPM0KFRO2S)_VnTXh2yEl3i1?T$|@yJZE>2= zdxG9uWFlYd{M%k!o~6=yPsDECVe{uj-`2ee|MA}5b>g9oqN^pz{;(4!Buw)up^b`4 z9hLM!SZg`i-xHf@>h18ZS6{)%9tF9T#OFQKQWM*o9(*0}IxsC(E{sm#i38iL6*yc~ zMn_PnlhdrM_~|ozfv?Te4qP`v5JHHjKMy205hWn?CQ`iElzRE3{e=$Ts$>0^j(?Tn z)PUZm=<>AK1DEMhdOkP`o)Zm54v9Cf0|A0j<$3 zSGOJ)Hyt35A_UpsBmA*BH*Q>Jbd1G%L+&cDe&S;S(bN;A(w}sOo9XAYp-h#vhmWg- zU2f#!z?Uzquk{-%gwVQwknd%vfoLHd5xeoMZQ+ZrY3DP*t&VXS7wGxC7MS%$L1^B0 zufLdU`ueU#bD}DksM}sVffm0;F!C8MWw8>O-C%TKAwPXgFvZhZNr!F^+Y4s?&;Sy; zy^|rt94TMt{xWbRItc6MMJf<{vNe_jk!_1b@iU1Z_Dx{2N@Q64YG00D{zwfx-<;n| zp#>XDo+X?Fq&xosWQsq*YfQR?Q{3HblK0it)f8`Uu#)f_wc~^7_S;0zqPM@XqvsE1 zW&;8>OdwxnZ4!HrYLy`ji z+QhE-HJ$ojXbu1ppT_ey6ToFtOt7q#UcfbR5qx<6yp!VYU@U|v={pFyu5@sTWFeHL z2SpIOswJC89+jJq6^d~md#3~TOJ@~pxgHSw!5%NiDC)yDx-Eo@x?}g_L)~UeZBDnR zE>-=mnill?&bo&6yKb&^T@=2X29tkf-ynz6^Pg!Xf1?;#vA+GEjE1+Lt2ivQ>lbE+ z(wqM!8Mu%sK=Bof!qS=gR=cDim^P}{LwCvf=T3S2bRK`-FHr(>;pDEv-sYdYrp6bX zTiw*Fh5U=K@%ocw(EYyaIgI)9Ko)c!jjr)2BB_{Z9<-Y8!RIf5!^ZsC`uy1mC3=V_ z_|r*e6t({ln53!AL!GWHKX9>%pCFF&>~EMlz+@s0 zdQyJ}qi;08R@n88_HR5=Yy4}4tIhw(KRkT=FUnbBi`A;V3doxl9)KkGG8OTEwQlCg zUFcNu6d{ndq%Jo#C?)>$Q|oQMKDbR4&UYkyHi_uif}59*<6R@vfn9+*`=P6+50E-$ zE^BmhVB!Eq10PGsW`rUDu>Sq01!$>cHWR;n{`T>rLA6)1>#Yy*)lRW2EWW-h`@GQS$1Mg{}{aTr-tMNne|{vOn(U)=^#UgD zT`-udc;MukP4`wa?nYidz$l#$(-h8 zFP`HxlXe|a5hwc~tw{MhCpBt2=k!PCnLBhN;KMeLw4=PI(F0@HS1-hsoBIssEXBuP z(GBFmer|KNoL?G@lx`2*Em{)|yIHce6T&rGq@#7Y6YVO-h;<)}=BEV|4$2nTxG^bh(#4@d8*QoZOp^%O)--t zFTNHB3U|%+K7Y%`n=@Qy5~lie=q(9jH8Sg>Sp&yCrVEKmkP`fL6x20K5m=*pYb~Y_0>;ARi?N~y`y`t&L3`t+ zv!p9>E81zfC-pFBQ`k9INTBz$_%!1r(!wu!)u^fF8BN;nOuDO$SX1rM4_*rSpL@PV z*(H5`lO}&Xl4w6!Rm3g3+~D6(?`jGSC_H~FvRD?Jm^;|A!Dmvf3ucYb27}n=AZ6Ac z@1+tI2XSpKx68*+m6#-T+1I@LK;ayel*Cr&x`U ztCffj&x#x{q^$o=?pUYS5Ac#Gl@mK&=R1#O^E?y@6Do5*65cQ=3sjZAi@V;^$$u8P9J78yiCym0X*vYkS^vs*ZsbH(Nw<{twjYgRgW|6(1dlR| zd+`J#p$Ghs~R*m)a!D`u{SUHun(b zGM()@y>vmP{6-}kBl7)&rL6nu?W5wEJ;h=`FNbiS5#0Z<0lK28Az@R&j+1YLdaB_l zEBPGbMm~X}s25UlOHoV(g&x5MILLnS!Jjbl^1`M183vw= zOg(vdMPy_A;a$BeY%5kiP^qcyDw?6b0nvFhfL_c|u)e(BFVK{?HsY$Zq&p&ZsGHW^ zclWi6s!g?c(B>0))wCE>nRC-dyrFBw+2m|dL@VpgK;F=vCM+|fOptDl3@js$1@_+u zacURa++Yw2_b~el%ymT2!Gmcsom?@=;=i|Az{5`~5EC^>&IUF!#QI0zPJS8g_NC zDBa(&EMgClUG2hUfKVk#A;4Gim`IGIdK;_?)UE{h zj&e^!%T}wbxEdOe>#|@~F5QjpmYXbBBBFmwK@rTk9seq(cbugl>>tER-O-CtNqI{I zoc5V*HJTT!ucIt=sNJgWP8ohLefMR`N=Wo95ih94-{YS3AIA%}n++H3SCdxBXRA42 zb?>%|Ak?%?1yc!e5N;N&=f3V<6chVXj8Wcj>;}>aZmhDrvH6XyOlL?O?&T$VzraIk zk3Ty@y&I6PzR{IL^fcP}EyAhRv>--aMQiawyX3h`^ki80cFTsnb-=?h?i&iaog9}( zGaU&VJ(r|W;3UgTlCJk|!A`e7YgfS<)5?~p3Y$FF(h3dOU59dv8TfzK^ zOawu+g)k3u9MOA(b*tnAP94x)oSCr?@pCJ&QYQXvC05Tm(wdO**fd>-Cl4pp8n40U zjo=!v2=v??ekp)j5DvJmQqD1PyixWqozii9*5z+6!1Dc;CTnNwTT9m&!^`fn1DOEO zTNsC_zucl#C_O1DQdyw>5igG;au6`_${!#WHh4dM)gZ4S1GcDDB%K1T=*wvZ=twuX)q$XcDp_tZyw0`)!wzV#!3 z@{TLMk(*#p1O7irxUap#EmLg|-!sMtUtnkKyi^SP1jJ@;{m4ubKKAXwA& z#s{IwmbA&|hK?VvdHWHQ&9CxowL5(&fzkbvyARIAZ(~otp_|;q1-i-mRDJ{@DJ9?? zEDfF2y-|{c+Q?#t-$|}9!mKN(9WTb_U+g8`a1o0wjGw~z(%p*)SrMfHBw4U?&$mWY z`60#yPFZ@?ZiU=BDIgk+$Gknr=hjO2pTa_qaX7I9Cxu$>8L9WsJbvkX*a^GS1KCe% zbd7-A0tey7{=I=3bP|BT%^SMa6b7U8w)5DcD}&x684BbvH6Zr3kQc$0?fAOByZfP= zI%%iB#ANAl*20f~EiSqoKUuUWXFQPmc3NTHbVjk5aJGzCh0o{Le`7a`IY35QeCvyc zYBobl)~-lZVv1lyIfYBb=MUe!SOw|lXhCL_E*Rg|{BsI4gJ^-JaYC)d;8RFTIXzea z86@zzWUy(&_)fco-0x5jLAf^$Q`#i_C23znCU>nfO*0v?3(=DSOy)HvX7`%G8dH&~ zrO+Qr*x948k06$qyKh*f8wVmj@aVAakS(4igENm4IhlFNesE)L0wfVY$ww2Xi`~DK z?6jy%BI2wz%Vm`0P4NAX5`GZZtV|ldp)Q4sJ$WPPL+HCdgk}G4lPmV-RkyKxsJ38X3Hy(I zVtSRH8FWZIor5>l649UCn0#FiFE$aJxIR(>;pq!l5=SaBnf$YmIX4!MllTvLElaCG zXv2IMQzKPpdOUW4phHRY@{MIQ|+DTL53IfHln; zQ-TBYF2<)CpKS9pl3po(2>g2TCa-$*ak#M~0(TYyeY|WUuoMN1jfmV@tI7O1uU)?z zJXsmtnMeess24E)h|+#GQ4v|SFS}M%;<~fd`e#8HvLaOrU$}=P&1F01zeB$La-Y{t z+E7r%I-@PTya5D7ALd9r{TH;=HE;JX_^HB9^;pN0)jpue$rM-o@IkP#&WHPtibrr$ z|3}=Ia~mhj%*eDawdi9jH-nB}Kuv4Do$OBftVDhtn(7&;_^LzXQs9)6oDDHvucO!G zQ0<{n`AY)freLzOXW^LiS(B}da)Z{a?myY$U=6Ss9-aq70 zXQa1b?lu+khdkA9U2W)yW`!|p3$sJnWq+x=QXlcSq+7%F6COj;NvE2*qwdR6&?l`E z)Yp%dBW-%fiIFMeDs9k%6X)xaDYSxNA zuj*jJ?w1w5Q}kblO@qnMkgU1ma(*+%jO|9gjZP!aT5sAs4WWzM)(>~hJU=L6!(qSK zGUL?CXYbcfI44wLw#;NzOfl~;Xu1ezF)AGS5Ur!nJ5ES(G0=E&V*RG(+wqfARAjAr zB1BV0tQ>k6!2`=1*Lt0<(%euKKaF8i*gbg-7D0MeShA(7&dD3(-|)k}uWDx5s{z~iB{Fe9-*L(3wQ+@-!c)Z@83 zi>kKw4JhIXqSisYq5}2l$oc`F6jz^6$Ojk*u~+4;UU7|l5XebgABsoUtU0H)Z$VxKOjL!Xvjrf>G)GSd_z*u8g zfv4`(*D;ha(_?A$r*Ab5k*|m6YeD%zPu_d=RTFF{_V*1cY0KWIs@4YfxX~(WQrr8U zphlKa!D@T5tNf?sq!5IF)N$Bo=_7MOU4s3N5p3iK>U|M;O5a7N$fS=$mYmKrVfj-h zy8V`-8Q!0h6WkSMToN$-Y4ba0ABl3$|Az6)>t*_1__)l=ZY7(FS9S95&^g?sj_?^* zGBs!& zgx?{&*N};FSUUyYcVfZCNGH=bJ=yX7$QLlDID-2)(y(iPd$kXw8B1?dCPLO@&rDlmvjxhDk zDC%^0C;9&r5npQ-UURNjm+WFb)Q|r+#+;MFLOIFUaI)FP2LLL99?vuqN zlwrf3NlaqjcJDtO8Z1Z!EpC2gcr?8pl|3herC2X}v*UI|KNu_i>!7)E5LBDW z`+7Ce|FUH4ntM3FM`1Ef!A~&FE07=sDGS2eME|6t|C3bT(c#7W& z>6Xua>h0jSXQ;OQfk$3`8^R1*oOS;+`0K|PKHHwx`EuSQlnJ9h zsWry#_r=R>^UCn(B)Yj>|7pit{;Ln)2M@7f;5v-hdwooPLR9jhdSa29>ODRQFT+&z zO1?YxDMHYL1La!pjS10y(vuyLjVy0_s_a~w)ci_48UR$~T$$Dx243AC#jolHj^>+2m+oD~_5 z_9)fEKcHOs3r-dS!clj&pL*A2am2()_?OpD0wo<WOQL`>IGvC*gg;tP|_>wA09(REaAkT#yF6ezv|+Vnk`|TAz`k6vG!vp z+p?aLaPIQ2UCNK&b(nE3M4cDocS`wbLa6K#hGx+IZjpFtv4x;MFN#(VSbfi~y?G z!nkC@D7~;yt&ITedGaql{m3leS;V&ch_E`_Y0#Lf;o02;&Wq0Whb|g<1i>$pKY?99 zV@nLjYeT_PFhbGg5mkEgt;M{Et)K5=sQd(sQ$F8}Ck4Bj9&%jIT1hw#?(c47gQEX> z_rKlvY7}xQyn#e1*9Li)&bXX+oRash3rYy=v+PSV4RD*k%oC>Gs*Hy6?LtpQ+b%!tuq8bTRt6`CJcn*TTSWs-qBEV|`kRx`gHiz;_! zG72WMRiC7-St(!_Wj}D~1a1wp1msHVh=nf1}Dzt&X?)wb{Rj|E}``CVmh0h?=C>+5u3vC_+Vt!3>&*Z!|}+U>W; z)}L!4l@qj!Ii zLfkd=7NW;`dx^B$S5rsEL3buCfQ!`taRD~+_;Y73u;l9}eq8h(es?d&99C4OBU@6X zRp6fZ+N{70073_((+6pq3f8J8%su7}M=p|mADWhOM2*Trgo0PZYTC0WkH81)3p1AIuE6XrTN{X(9E};N&lmz=lP=2CrTlu}k1AUS*KT21#*IJe zwy}@BiWcV{TcH#*2UJJEUMNarT6@$*>W_&EtcvGh>H{W%sp$a&9l36z?@e5m+i_a?V6cF=BK6@F0< z!xlzeL{Yvoa2Tvf$XJd7dkQ}_FuSnT_0H|w{2tWMI=h%?0@k^Nq_^@5xhJ!?nY?S0 z3)N(GQNb_XgU7$iNYj5@O$3>ieoDQqEzXGUZokTfDq{@Dk8miLFv8sYN3Q6Na$U9 z4?UDX@*cnMFZW-#YuzkZfh1?2oxS(WJoC&m5xUx{l;lk0002;`t3A~R07CFjLV)xp z_;m7S>=Jw;@_wRjND4jzN$o#@|C7B^GxY`ls$c(p36ceZQ$Yf8gA?5 z0KnmJVVJv%x1H@P2VoB{r%d!?CIDau)SoIDzRB8~ho`ZOr|%ywmTZgW=_%%YiX$Zt zRb;p2ROGaa>kdWSB#9EMzF9STvxpL2WxDS_iSZBGJ7s#M_~yG+rmGhf_a|O2eMOlK zNeR}{?KuTAnkT5ilh?*R(6c~=;XKvB=i@R}Y3Rx(?d;=wWZ>lf-{b$<6?p&i7ptQ2 zgtgxqV8F}5-Y27|B#P1qT9v+z9<#65V}=I;fZB{}G)zK2Ghgcw=_6^>T!ouko$9>5 zi|X8WA8s+S?m~gem#0B^2A9DR6)sX#Y4PAs z#g^v_IPAqYpxJ=X_tgnsY9|(oUmP|nb>Xkq&3vuuz#-RCW2xT#H5Xrhp?>W=E&>2P zxxdY@F3dy<_X1avbgMm@52o5 z?A-wVXbMX8UR`%HO1R%@ThgTs6-u=buTRsMXWxnvoh1fxQ`@A3EEo1aGEcl8w|Pm{ z{ShgGPiw%y6I9-ha{~ac7>a)}ZJ6?l*vd%UOj%6w;KxW2T)KFAm73R+PoRbErt`RT zL0;jnLu@KLWs3{HmeK$LYsoUV7^7H@35b+q{*1l7jYd-k(4R(fAW^EYY;fKXD_ zMO4$i5N5l~VC{>pFJbj|K`OfD!;kODJ;VU;024tONsrH;3qxuE6IsER^jh*dJ_-Pk zFgR0H(Y0GUoaFto2?q+N6re3Kg}a~!(rJZ%sA5lsOAPZ4T~QYU-ZX!n@uI_PL& z^rPOvnu*qVpI;o{ve6XHn*?ljFXDWcz64Kgk%oF^<)nHak^n%&SRd2z=umKTACb*C zU(xdhrS9?&0N9?D*{Nz?9+bPbX(H;@=#?INIMPQ1el>a`yz|*Xe@GL!J@v2+a~Ga* zZhio`U8Bw{+;z3m7VNFV0ay+(fuHs1oe7yaHD}WRjC6A35o!i5^#6g|^R|S!vU&Me zcGuxoBKB&GOG7j`jj?wP7!hBbzbaw%;h+QwlMAwx?G`wVsD_Oy+)*ml9q*+|sLOjt zUck=3K2;PPhFNIK0)4e-y{iVe-nO`-^B4efxyw83u^~+AW`5fx%38MtQw{aj6f_8K z0J`*nc6)!{c3toFqDVsg`QmzCqygT)eq4KzwL8;5Zd8@>xC(P9 z$xt^eFCMNVcs*bAY$%~8p}{j8`rqsVcbje4I|t;QaudBKe9XtSR`Gy6t|Fhf=lEcK zS&l>v^`Hz*1>__3iGg)l&c#$OKhxpa!+-{6sR8$hC5d9~DO7RS;KxMmnX)*I3S)Zn z8}M)$pd(65oR+&kEBK@2>oni%%|Uw!jRO6e_l>F(1C`cQHouvOHB*R2ivo8o?a@Kj9_1eaH0C@BVF z)tE@V{81ffq#&fr3DNEeK*!HFi=kgKB`}IVMSl$+V1;O-JFx?|81=F2?-aiut&y(} z#-A;;k*^OW|6X%uQynDSVyN?hgD-<0-RhJmHzf0 zGB0Ww&My?f@OO&1Zy(4Pf>jsFs!lTW*zE=j9M?{^6eK2sJ2F110ZchE_(W7qUy6^b z95(Zzi8I{K=xTLh3ib!F8hl=4HjdHvO<|oqXN@3wdx!<)ig^v$M zk@D@8w4vEVk@RKf2g*lQeg$v~B5oFL?=AX)V9d-PvhGJcB=>Ze&~3|9dBV~^!v!9m zniRyyYB*MPEK~8^`Tp%0Kkj1q$U0cP(Ig6zrOW8zAtg|x6#%-EGT0}gT+j4~#Qb4P zR34d3bOPW?FKCwkL#U}g2Om85^{La8XnGaCA?bHYkcNjEU|(@0H5do3v8QT+2|BGt zBgZ#mj*PZ6r#V%(F;5wHJY04#l~=nn(Zi~3VkKHusAbLxTXZ(NPw-NAt9}rUv)Zc%GeSrloxuj8G-+AE%d$mCeIm;=1=Y?g6BPvd%F&Iw!Jvu|vN zr9ZNief=bM*xR5o%blBig4J#{ilt!euy5Z)VS#t>kVj25Fu!l3Kw^vZoBF+#R`DS0 zK*-*Bv$8r79L4$2@|3GU=dqr4*sasl-wZO?-Mo_swqyzWS6kk>!Kt(x`U%ZmcJTD5 zlaa*bmJh2EAj-Y3mBnlD z-SX>LE(g%|N+rf{yPsoi_>1KtU0+N(zxVkPUG|K*3c#8C+1dehWE~)cR&ySzZ{!a+ z+(^1u~PZ&0W(tO7HTVcI-+AZ(PC=>pDdiY%3}xG!VEh;f%wE#I@lGJi`qt z@6d@&j9 zdC$Wtll3c#B>7Hl-$``ggaRhFMNA|Lox~@;h=ZgPTqh&!m#K~UPWZ8GJ z2a7}f9iqf@E~4flXjfwL1E_xDpLvk3_(y zP?)nmrc?wWI=G;F)!?#Bb)RLrd@kf%p?22jp?xi5`u4#0Tng@Nqsyq(Xu>!E_|Nkr zrNR30a1ZOvCwBxbnc$@3JEN*-1zh4gH-|ujtt1Phu&2U7yPlv4^sx4Z|GPwk)z7}< z6Z!U+-y3v;Ic#tSsMs|Yd7*&M&((o(@bvklofr$maGX{DT#i{*Rs%vmIS5}GeoH0% z`WUxUrTK*(&Oj#tTGYkRBGOTV_Bu*zz!AJev*f)N6*mF!!140T`10y_h3dw5wb25t z&M($yYZ;kqSMv(IQ?Qf}{=@{&H)w0kAp%4xr#c}prs%(2<3-KD3TG&eI~x!69$gau8=vsT}AK?pCG~QbDku?;I0^5P7wFM&puaxV*XHjK;5GBWPi?w zlsCFZL*B81i^JsA7nR(G$9aao6~B|?XZ8W&9t(;3oK|C3Fe=|Udp5cyNgVRR{@8E& z$$hk_ugxTVZtq-)!J3E#1Ds(qYNO;JT%lO{c)FRR1avfmYa#dBkWJBIUIvKt&YVuBQscOekjll#F3t@B>*V>LI|A9-NY2d*Z;v~JUs&O! zY(Gc=UAw{-Q*=JZYRCDLxvz=|dz*3iOP3?u)kZ5@y4YwTl4>!CmLH8v{2p+c+^l?l zn+D+2ueOf>gO;Y-o*bkmL;?0o9`*M-%Bn(t6rDU26}6g#^JP)(6?@nv0iGdFve0~+ z3oqhGdc;Kak1vOT(=k-jr#p0lmi!@y5BNx(CqnFRQYOgkt+Y{IYwGhSmm=Fj+C1}i zOQA=`RS7c?d&rCf1#$X|tjC^bU z`M>GK;AdP0uGc3M_KhcXH6sT{RFNz`QM-RZnBo`}?-_+EdHoxmkadGvkdCMXvuM&s z4xg)B&WqLX)kzgR`6T9rTjrnE7Czc~edL6jwb1_F?sv2?q$djh@@@@je|+(I<{!9o zT2*VY>qE$FvbSK-0zK^~HE$M!YA{_qoPb)^)n3yFZ(CQF#GotB={~m9-q=)`V?o&Y z!8}#n#K5JIpMr>iq(;%GM!~lrm$X9e7LuqG1rPW9YtCc^c+BV6$24$;=oLG*MAq63 zPN`%Kz7krzn`(Hwv32~}4+`@2-xO|~z3y|oAuB2u9vxij;zB{m8Pa;_RfHF{Z1FlG z6hC1Od7(}NZr11DYxa|slwC{k#pG-`bH|z2u*;I{yDQaiq4%xh_ObE%@#q+~MHkvo z8+3cK@=KPW%l@oZY(m%GH*H6OrQs0kg_T%j$O`533$Y`d>#Px-lYH6opp#~Rch;+K z`aEAu5=#Spj~&-Mu5$XZB~Op682^))F3#Zw3>HyDWg`BS5w1l%ffDf9;aHtqcUfDrpHHM2EyLj*& zXLkh6?<&<7KTCg(U1t^#xl-mM+x({BQsKoq!bLE2@180kXRER&ptTtodyzkPjisP3 zeRKR9F{6t(HVnit(pkT;Dp`p!STCxEGr&4(5d?dKA^w&j?42b# z!d(a1f3;Hye^i^?aJ!Bwx=K_&Xhv9PvzPyV0|P8YRWFP01IQZ zb)cRS54NoD(ecA%{mYZl>~RZ$1{waYP2rbT=VYM>BLxNG z4js6+aU*?GX@Sj}#u^U00mAO_`o-euCrY^X7D^fY)VS08a&{jn6OTJWwoaBkp6_Y4 zq3{0u``!7nS1MWdVs*xP?2m+ld@b44LF(I#_09E?TqR02;k`EN)bRYdnT6Sl<3(e1 zAAaN&Jzm8aP8OOjs_+Yt^VFV7+;zfI`?% z=2=B{npDsf?Z)4*@sr7rpK%`WnW3aFk5+4`A}{|d#`iKGeR=Z$C9lhj z&;p|3FP5RT$zz8)m{aS7B>Cg#G9+v)a0Y|{QWc2d@V-oUV9n4j;_!R?pQ{O*f96Zw zaBbe5YF=7kcI4|78?H%M;K~*rp#==8>LjQ{e}+VDGUOE^g++GIb;czQgs)J2`TudW z$_;vEt6>P@@@@8J)MlQAF-lk0AW9#sQM&9`R*|<Wp&BRkyd=r{k5Ym7^yU{a6ah&R-1VskYsk_QFxb** zt$fm2<|oMT9x7>#?w&AC*^Xl2gD*e5sOq>2=RK5A89G=3NfYX(`I;5`A=$E72g6z5y?QwOqc zu|BKeSLa^{vF6-+V1>{CdSen*b`5+alvt?@9EPL~SgX1gT*BYOQ)G5r*Md)x+jwiq zhX@uKQ`vO}*nTAswk1YDYGuNmmTuTUG}UKp2&8;or0p_;&}+YkepT-J^%2rLHQ=o3 zB{wkRv(t?A(2dXl2BufPu{K1#A&O3p5YCuj$PMCg-7rH3tg@oer1QsO-KZ8F|f0~*5%bQb#b_1 zetcbBmi$E)>PG{pCqNusm33lHto-@Vx7iW(C(!k|<0M(HTCYhf4)gV)xQFAvH=+6E z$!Ax`ho`Y>TuwB9;}ey$NjHcQgX?X}+Okgy6Mm=`3pAP};b+P~Jf1C`r&4mkhSx(+ zZF94S4|&>$Q}JP6x%}ubpmrSAcGQ2b%gIe3nixac(9vDhy=WN+u;9RN@ zzSSg(sI-*Mf#5wCFU+h6T324Q8p;jDt=rlv5FRY}LB=p~f(SL>@>HPgfw>M*u&yIon){!Um{TPM0KFRO2S)_VnTXh2yEl3i1?T$|@yJZE>2= zdxG9uWFlYd{M%k!o~6=yPsDECVe{uj-`2ee|MA}5b>g9oqN^pz{;(4!Buw)up^b`4 z9hLM!SZg`i-xHf@>h18ZS6{)%9tF9T#OFQKQWM*o9(*0}IxsC(E{sm#i38iL6*yc~ zMn_PnlhdrM_~|ozfv?Te4qP`v5JHHjKMy205hWn?CQ`iElzRE3{e=$Ts$>0^j(?Tn z)PUZm=<>AK1DEMhdOkP`o)Zm54v9Cf0|A0j<$3 zSGOJ)Hyt35A_UpsBmA*BH*Q>Jbd1G%L+&cDe&S;S(bN;A(w}sOo9XAYp-h#vhmWg- zU2f#!z?Uzquk{-%gwVQwknd%vfoLHd5xeoMZQ+ZrY3DP*t&VXS7wGxC7MS%$L1^B0 zufLdU`ueU#bD}DksM}sVffm0;F!C8MWw8>O-C%TKAwPXgFvZhZNr!F^+Y4s?&;Sy; zy^|rt94TMt{xWbRItc6MMJf<{vNe_jk!_1b@iU1Z_Dx{2N@Q64YG00D{zwfx-<;n| zp#>XDo+X?Fq&xosWQsq*YfQR?Q{3HblK0it)f8`Uu#)f_wc~^7_S;0zqPM@XqvsE1 zW&;8>OdwxnZ4!HrYLy`ji z+QhE-HJ$ojXbu1ppT_ey6ToFtOt7q#UcfbR5qx<6yp!VYU@U|v={pFyu5@sTWFeHL z2SpIOswJC89+jJq6^d~md#3~TOJ@~pxgHSw!5%NiDC)yDx-Eo@x?}g_L)~UeZBDnR zE>-=mnill?&bo&6yKb&^T@=2X29tkf-ynz6^Pg!Xf1?;#vA+GEjE1+Lt2ivQ>lbE+ z(wqM!8Mu%sK=Bof!qS=gR=cDim^P}{LwCvf=T3S2bRK`-FHr(>;pDEv-sYdYrp6bX zTiw*Fh5U=K@%ocw(EYyaIgI)9Ko)c!jjr)2BB_{Z9<-Y8!RIf5!^ZsC`uy1mC3=V_ z_|r*e6t({ln53!AL!GWHKX9>%pCFF&>~EMlz+@s0 zdQyJ}qi;08R@n88_HR5=Yy4}4tIhw(KRkT=FUnbBi`A;V3doxl9)KkGG8OTEwQlCg zUFcNu6d{ndq%Jo#C?)>$Q|oQMKDbR4&UYkyHi_uif}59*<6R@vfn9+*`=P6+50E-$ zE^BmhVB!Eq10PGsW`rUDu>Sq01!$>cHWR;n{`T>rLA6)1>#Yy*)lRW2EWW-h`@GQS$1Mg{}{aTr-tMNne|{vOn(U)=^#UgD zT`-udc;MukP4`wa?nYidz$l#$(-h8 zFP`HxlXe|a5hwc~tw{MhCpBt2=k!PCnLBhN;KMeLw4=PI(F0@HS1-hsoBIssEXBuP z(GBFmer|KNoL?G@lx`2*Em{)|yIHce6T&rGq@#7Y6YVO-h;<)}=BEV|4$2nTxG^bh(#4@d8*QoZOp^%O)--t zFTNHB3U|%+K7Y%`n=@Qy5~lie=q(9jH8Sg>Sp&yCrVEKmkP`fL6x20K5m=*pYb~Y_0>;ARi?N~y`y`t&L3`t+ zv!p9>E81zfC-pFBQ`k9INTBz$_%!1r(!wu!)u^fF8BN;nOuDO$SX1rM4_*rSpL@PV z*(H5`lO}&Xl4w6!Rm3g3+~D6(?`jGSC_H~FvRD?Jm^;|A!Dmvf3ucYb27}n=AZ6Ac z@1+tI2XSpKx68*+m6#-T+1I@LK;ayel*Cr&x`U ztCffj&x#x{q^$o=?pUYS5Ac#Gl@mK&=R1#O^E?y@6Do5*65cQ=3sjZAi@V;^$$u8P9J78yiCym0X*vYkS^vs*ZsbH(Nw<{twjYgRgW|6(1dlR| zd+`J#p$Ghs~R*m)a!D`u{SUHun(b zGM()@y>vmP{6-}kBl7)&rL6nu?W5wEJ;h=`FNbiS5#0Z<0lK28Az@R&j+1YLdaB_l zEBPGbMm~X}s25UlOHoV(g&x5MILLnS!Jjbl^1`M183vw= zOg(vdMPy_A;a$BeY%5kiP^qcyDw?6b0nvFhfL_c|u)e(BFVK{?HsY$Zq&p&ZsGHW^ zclWi6s!g?c(B>0))wCE>nRC-dyrFBw+2m|dL@VpgK;F=vCM+|fOptDl3@js$1@_+u zacURa++Yw2_b~el%ymT2!Gmcsom?@=;=i|Az{5`~5EC^>&IUF!#QI0zPJS8g_NC zDBa(&EMgClUG2hUfKVk#A;4Gim`IGIdK;_?)UE{h zj&e^!%T}wbxEdOe>#|@~F5QjpmYXbBBBFmwK@rTk9seq(cbugl>>tER-O-CtNqI{I zoc5V*HJTT!ucIt=sNJgWP8ohLefMR`N=Wo95ih94-{YS3AIA%}n++H3SCdxBXRA42 zb?>%|Ak?%?1yc!e5N;N&=f3V<6chVXj8Wcj>;}>aZmhDrvH6XyOlL?O?&T$VzraIk zk3Ty@y&I6PzR{IL^fcP}EyAhRv>--aMQiawyX3h`^ki80cFTsnb-=?h?i&iaog9}( zGaU&VJ(r|W;3UgTlCJk|!A`e7YgfS<)5?~p3Y$FF(h3dOU59dv8TfzK^ zOawu+g)k3u9MOA(b*tnAP94x)oSCr?@pCJ&QYQXvC05Tm(wdO**fd>-Cl4pp8n40U zjo=!v2=v??ekp)j5DvJmQqD1PyixWqozii9*5z+6!1Dc;CTnNwTT9m&!^`fn1DOEO zTNsC_zucl#C_O1DQdyw>5igG;au6`_${!#WHh4dM)gZ4S1GcDDB%K1T=*wvZ=twuX)q$XcDp_tZyw0`)!wzV#!3 z@{TLMk(*#p1O7irxUap#EmLg|-!sMtUtnkKyi^SP1jJ@;{m4ubKKAXwA& z#s{IwmbA&|hK?VvdHWHQ&9CxowL5(&fzkbvyARIAZ(~otp_|;q1-i-mRDJ{@DJ9?? zEDfF2y-|{c+Q?#t-$|}9!mKN(9WTb_U+g8`a1o0wjGw~z(%p*)SrMfHBw4U?&$mWY z`60#yPFZ@?ZiU=BDIgk+$Gknr=hjO2pTa_qaX7I9Cxu$>8L9WsJbvkX*a^GS1KCe% zbd7-A0tey7{=I=3bP|BT%^SMa6b7U8w)5DcD}&x684BbvH6Zr3kQc$0?fAOByZfP= zI%%iB#ANAl*20f~EiSqoKUuUWXFQPmc3NTHbVjk5aJGzCh0o{Le`7a`IY35QeCvyc zYBobl)~-lZVv1lyIfYBb=MUe!SOw|lXhCL_E*Rg|{BsI4gJ^-JaYC)d;8RFTIXzea z86@zzWUy(&_)fco-0x5jLAf^$Q`#i_C23znCU>nfO*0v?3(=DSOy)HvX7`%G8dH&~ zrO+Qr*x948k06$qyKh*f8wVmj@aVAakS(4igENm4IhlFNesE)L0wfVY$ww2Xi`~DK z?6jy%BI2wz%Vm`0P4NAX5`GZZtV|ldp)Q4sJ$WPPL+HCdgk}G4lPmV-RkyKxsJ38X3Hy(I zVtSRH8FWZIor5>l649UCn0#FiFE$aJxIR(>;pq!l5=SaBnf$YmIX4!MllTvLElaCG zXv2IMQzKPpdOUW4phHRY@{MIQ|+DTL53IfHln; zQ-TBYF2<)CpKS9pl3po(2>g2TCa-$*ak#M~0(TYyeY|WUuoMN1jfmV@tI7O1uU)?z zJXsmtnMeess24E)h|+#GQ4v|SFS}M%;<~fd`e#8HvLaOrU$}=P&1F01zeB$La-Y{t z+E7r%I-@PTya5D7ALd9r{TH;=HE;JX_^HB9^;pN0)jpue$rM-o@IkP#&WHPtibrr$ z|3}=Ia~mhj%*eDawdi9jH-nB}Kuv4Do$OBftVDhtn(7&;_^LzXQs9)6oDDHvucO!G zQ0<{n`AY)freLzOXW^LiS(B}da)Z{a?myY$U=6Ss9-aq70 zXQa1b?lu+khdkA9U2W)yW`!|p3$sJnWq+x=QXlcSq+7%F6COj;NvE2*qwdR6&?l`E z)Yp%dBW-%fiIFMeDs9k%6X)xaDYSxNA zuj*jJ?w1w5Q}kblO@qnMkgU1ma(*+%jO|9gjZP!aT5sAs4WWzM)(>~hJU=L6!(qSK zGUL?CXYbcfI44wLw#;NzOfl~;Xu1ezF)AGS5Ur!nJ5ES(G0=E&V*RG(+wqfARAjAr zB1BV0tQ>k6!2`=1*Lt0<(%euKKaF8i*gbg-7D0MeShA(7&dD3(-|)k}uWDx5s{z~iB{Fe9-*L(3wQ+@-!c)Z@83 zi>kKw4JhIXqSisYq5}2l$oc`F6jz^6$Ojk*u~+4;UU7|l5XebgABsoUtU0H)Z$VxKOjL!Xvjrf>G)GSd_z*u8g zfv4`(*D;ha(_?A$r*Ab5k*|m6YeD%zPu_d=RTFF{_V*1cY0KWIs@4YfxX~(WQrr8U zphlKa!D@T5tNf?sq!5IF)N$Bo=_7MOU4s3N5p3iK>U|M;O5a7N$fS=$mYmKrVfj-h zy8V`-8Q!0h6WkSMToN$-Y4ba0ABl3$|Az6)>t*_1__)l=ZY7(FS9S95&^g?sj_?^* zGBs!& zgx?{&*N};FSUUyYcVfZCNGH=bJ=yX7$QLlDID-2)(y(iPd$kXw8B1?dCPLO@&rDlmvjxhDk zDC%^0C;9&r5npQ-UURNjm+WFb)Q|r+#+;MFLOIFUaI)FP2LLL99?vuqN zlwrf3NlaqjcJDtO8Z1Z!EpC2gcr?8pl|3herC2X}v*UI|KNu_i>!7)E5LBDW z`+7Ce|FUH4ntM3FM`1Ef!A~&FE07=sDGS2eME|6t|C3bT(c#7W& z>6Xua>h0jSXQ;OQfk$3`8^R1*oOS;+`0K|PKHHwx`EuSQlnJ9h zsWry#_r=R>^UCn(B)Yj>|7pit{;Ln)2M@7f;5v-hdwooPLR9jhdSa29>ODRQFT+&z zO1?YxDMHYL1La!pjS10y(vuyLjVy0_s_a~w)ci_48UR$~T$$Dx243AC#jolHj^>+2m+oD~_5 z_9)fEKcHOs3r-dS!clj&pL*A2am2()_?OpD0wo<WOQL`>IGvC*gg;tP|_>wA09(REaAkT#yF6ezv|+Vnk`|TAz`k6vG!vp z+p?aLaPIQ2UCNK&b(nE3M4cDocS`wbLa6K#hGx+IZjpFtv4x;MFN#(VSbfi~y?G z!nkC@D7~;yt&ITedGaql{m3leS;V&ch_E`_Y0#Lf;o02;&Wq0Whb|g<1i>$pKY?99 zV@nLjYeT_PFhbGg5mkEgt;M{Et)K5=sQd(sQ$F8}Ck4Bj9&%jIT1hw#?(c47gQEX> z_rKlvY7}xQyn#e1*9Li)&bXX+oRash3rYy=v+PSV4RD*k%oC>Gs*Hy6?LtpQ+b%!tuq8bTRt6`CJcn*TTSWs-qBEV|`kRx`gHiz;_! zG72WMRiC7-St(!_Wj}D~1a1wp1msHVh=nf1}Dzt&X?)wb{Rj|E}``CVmh0h?=C>+5u3vC_+Vt!3>&*Z!|}+U>W; z)}L!4l@qj!Ii zLfkd=7NW;`dx^B$S5rsEL3buCfQ!`taRD~+_;Y73u;l9}eq8h(es?d&99C4OBU@6X zRp6fZ+N{70073_((+6pq3f8J8%su7}M=p|mADWhOM2*Trgo0PZYTC0WkH81)3p1AIuE6XrTN{X(9E};N&lmz=lP=2CrTlu}k1AUS*KT21#*IJe zwy}@BiWcV{TcH#*2UJJEUMNarT6@$*>W_&EtcvGh>H{W%sp$a&9l36z?@e5m+i_a?V6cF=BK6@F0< z!xlzeL{Yvoa2Tvf$XJd7dkQ}_FuSnT_0H|w{2tWMI=h%?0@k^Nq_^@5xhJ!?nY?S0 z3)N(GQNb_XgU7$iNYj5@O$3>ieo 0) { - if (streaming.intervalId) { - clearInterval(streaming.intervalId); - } - streaming.intervalId = setInterval(() => { - if (!me.realtime.pause && typeof me.realtime.onRefresh === 'function') { - me.realtime.onRefresh(chart); - } - }, me.realtime.refresh); - console.log('📈 RealTimeScale data interval started:', me.realtime.refresh + 'ms'); - } - - // Configurar intervalo de render (frameRate) - const fps = Math.max(1, me.realtime.frameRate || 30); - const frameIntervalMs = Math.round(1000 / fps); - if (streaming.frameIntervalId) { - clearInterval(streaming.frameIntervalId); - } - streaming.frameIntervalId = setInterval(() => { - if (!me.realtime.pause) { - me.updateRealTimeData(); - chart.update('quiet'); - } - }, frameIntervalMs); - console.log('🎞️ RealTimeScale render interval started:', frameIntervalMs + 'ms (' + fps + ' fps)'); - } - - updateRealTimeData() { - const me = this; - const chart = me.chart; - - if (!chart.data || !chart.data.datasets) { - return; - } - - const now = Date.now(); - const duration = me.realtime.duration; - const delay = me.realtime.delay; - const ttl = me.realtime.ttl || duration * 2; // TTL por defecto - - // Calcular ventana de tiempo - me.max = now - delay; - me.min = me.max - duration; - - // Limpiar datos antiguos automáticamente - const cutoff = now - ttl; - chart.data.datasets.forEach(dataset => { - if (dataset.data) { - const oldLength = dataset.data.length; - dataset.data = dataset.data.filter(point => point.x > cutoff); - if (oldLength !== dataset.data.length) { - console.log(`📈 Cleaned ${oldLength - dataset.data.length} old points from ${dataset.label}`); - } - } - }); - } - - update(args) { - this.updateRealTimeData(); - super.update(args); - } - - destroy() { - const me = this; - const chart = me.chart; - const streaming = chart.$streaming; - - if (streaming) { - if (streaming.intervalId) { - clearInterval(streaming.intervalId); - delete streaming.intervalId; - console.log('📈 RealTimeScale data interval cleared'); - } - if (streaming.frameIntervalId) { - clearInterval(streaming.frameIntervalId); - delete streaming.frameIntervalId; - console.log('🎞️ RealTimeScale render interval cleared'); - } - } - - super.destroy(); - } - - } - - // Configurar ID y defaults fuera de la clase para mejor compatibilidad - RealTimeScale.id = 'realtime'; - RealTimeScale.defaults = { - realtime: { - duration: 10000, - delay: 0, - refresh: 1000, - frameRate: 30, - pause: false, - ttl: undefined, - onRefresh: null - }, - time: { - unit: 'second', - displayFormats: { - second: 'HH:mm:ss' - } - } - }; - - // ============= PLUGIN.STREAMING.JS (Simplificado) ============= - const streamingPlugin = { - id: 'streaming', - - beforeInit(chart) { - const streaming = chart.$streaming = chart.$streaming || {}; - streaming.enabled = false; - - // Detectar si hay escalas realtime - const scales = chart.options.scales || {}; - Object.keys(scales).forEach(scaleId => { - if (scales[scaleId].type === 'realtime') { - streaming.enabled = true; - } - }); - }, - - afterInit(chart) { - const streaming = chart.$streaming; - if (streaming && streaming.enabled) { - // Configurar actualización automática - const update = chart.update; - chart.update = function (mode) { - if (mode === 'quiet') { - // Actualización silenciosa para streaming - Chart.prototype.update.call(this, mode); - } else { - update.call(this, mode); - } - }; - } - }, - - beforeUpdate(chart) { - const streaming = chart.$streaming; - if (!streaming || !streaming.enabled) return; - - // Permitir que las líneas Bézier se extiendan fuera del área del gráfico - const elements = chart.options.elements || {}; - if (elements.line) { - elements.line.capBezierPoints = false; - } - }, - - destroy(chart) { - const streaming = chart.$streaming; - if (streaming && streaming.intervalId) { - clearInterval(streaming.intervalId); - delete streaming.intervalId; - } - delete chart.$streaming; - } - }; - - // ============= REGISTRO DE COMPONENTES ============= - - // Intentar registro simple de componentes - function tryRegisterComponents() { - if (typeof Chart !== 'undefined' && Chart.register) { - try { - console.log('📈 Attempting Chart.js streaming components registration...'); - - // Registrar escala realtime - Chart.register(RealTimeScale); - - // Registrar plugin de streaming - Chart.register(streamingPlugin); - - console.log('📈 Streaming components registration attempt completed'); - console.log('📈 RealTimeScale available:', !!Chart.registry.scales.realtime); - - if (Chart.registry.scales.realtime) { - console.log('✅ RealTimeScale registered successfully'); - window.chartStreamingRegistered = true; - } else { - console.log('⚠️ RealTimeScale registration may have failed - fallback mode will be used'); - } - - } catch (error) { - console.log('⚠️ Chart.js streaming components registration failed:', error.message); - console.log('📋 Fallback mode will be used instead (this is perfectly fine)'); - } - } else { - console.log('⚠️ Chart.js not ready for component registration'); - } - } - - // Intentar registro una vez inmediatamente - tryRegisterComponents(); - - // Y también cuando el DOM esté listo (por si acaso) - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', tryRegisterComponents); - } - - // ============= UTILIDADES PARA LA APLICACIÓN ============= - - /** - * Crea una configuración de Chart.js optimizada para streaming - */ - function createStreamingChartConfig(options = {}) { - const config = { - type: 'line', - data: { - datasets: [] - }, - options: { - responsive: true, - maintainAspectRatio: false, - animation: false, // Desactivar animaciones para mejor performance - - scales: { - x: { - type: 'realtime', - realtime: { - duration: options.duration || 60000, // 60 segundos por defecto - delay: options.delay || 0, - refresh: options.refresh || 1000, // 1 segundo - frameRate: options.frameRate || 30, - pause: options.pause || false, - ttl: options.ttl || undefined, - onRefresh: options.onRefresh || null - }, - title: { - display: true, - text: 'Tiempo' - } - }, - y: { - title: { - display: true, - text: 'Valor' - }, - min: options.yMin, - max: options.yMax - } - }, - - plugins: { - legend: { - display: true, - position: 'top' - }, - tooltip: { - mode: 'index', - intersect: false - } - }, - - interaction: { - mode: 'nearest', - axis: 'x', - intersect: false - }, - - elements: { - point: { - radius: 0, // Sin puntos para mejor performance - hoverRadius: 3 - }, - line: { - tension: 0.1, - borderWidth: 2 - } - } - }, - plugins: ['streaming'] - }; - - return config; - } - - /** - * Agrega datos a un dataset de streaming - */ - function addStreamingData(chart, datasetIndex, data) { - if (!chart || !chart.data || !chart.data.datasets[datasetIndex]) { - console.warn(`📈 Cannot add streaming data - chart or dataset ${datasetIndex} not found`); - return false; - } - - const dataset = chart.data.datasets[datasetIndex]; - if (!dataset.data) { - dataset.data = []; - } - - // Agregar nuevo punto con timestamp - const timestamp = data.x || Date.now(); - const newPoint = { - x: timestamp, - y: data.y - }; - - dataset.data.push(newPoint); - - console.log(`📈 Added point to dataset ${datasetIndex} (${dataset.label}): x=${timestamp}, y=${data.y}`); - - // Chart.js se encarga automáticamente de eliminar datos antiguos - // basado en la configuración de TTL y duration de la escala realtime - return true; - } - - /** - * Controla la pausa/reanudación del streaming - */ - function setStreamingPause(chart, paused) { - if (!chart || !chart.$streaming) return; - - const scales = chart.scales; - Object.keys(scales).forEach(scaleId => { - const scale = scales[scaleId]; - if (scale instanceof RealTimeScale) { - scale.realtime.pause = paused; - } - }); - } - - /** - * Limpia todos los datos de streaming - */ - function clearStreamingData(chart) { - if (!chart || !chart.data) return; - - chart.data.datasets.forEach(dataset => { - if (dataset.data) { - dataset.data.length = 0; - } - }); - - chart.update('quiet'); - } - - // ============= EXPORTS ============= - - // Exportar para uso en la aplicación - exports.RealTimeScale = RealTimeScale; - exports.streamingPlugin = streamingPlugin; - exports.createStreamingChartConfig = createStreamingChartConfig; - exports.addStreamingData = addStreamingData; - exports.setStreamingPause = setStreamingPause; - exports.clearStreamingData = clearStreamingData; - - // Hacer disponible globalmente - if (typeof window !== 'undefined') { - window.ChartStreaming = { - createStreamingChartConfig, - addStreamingData, - setStreamingPause, - clearStreamingData, - RealTimeScale, - streamingPlugin - }; - } - - console.log('📈 Chart.js Streaming Plugin loaded successfully'); -}); \ No newline at end of file diff --git a/static/js/chartjs-streaming/helpers.streaming.js b/static/js/chartjs-streaming/helpers.streaming.js deleted file mode 100644 index 9b4c1c8..0000000 --- a/static/js/chartjs-streaming/helpers.streaming.js +++ /dev/null @@ -1,85 +0,0 @@ -import {callback as call, each, noop, requestAnimFrame, valueOrDefault} from 'chart.js/helpers'; - -export function clamp(value, lower, upper) { - return Math.min(Math.max(value, lower), upper); -} - -export function resolveOption(scale, key) { - const realtimeOpts = scale.options.realtime; - const streamingOpts = scale.chart.options.plugins.streaming; - return valueOrDefault(realtimeOpts[key], streamingOpts[key]); -} - -export function getAxisMap(element, {x, y}, {xAxisID, yAxisID}) { - const axisMap = {}; - - each(x, key => { - axisMap[key] = {axisId: xAxisID}; - }); - each(y, key => { - axisMap[key] = {axisId: yAxisID}; - }); - return axisMap; -} - -/** -* Cancel animation polyfill -*/ -const cancelAnimFrame = (function() { - if (typeof window === 'undefined') { - return noop; - } - return window.cancelAnimationFrame; -}()); - -export function startFrameRefreshTimer(context, func) { - if (!context.frameRequestID) { - const refresh = () => { - const nextRefresh = context.nextRefresh || 0; - const now = Date.now(); - - if (nextRefresh <= now) { - const newFrameRate = call(func); - const frameDuration = 1000 / (Math.max(newFrameRate, 0) || 30); - const newNextRefresh = context.nextRefresh + frameDuration || 0; - - context.nextRefresh = newNextRefresh > now ? newNextRefresh : now + frameDuration; - } - context.frameRequestID = requestAnimFrame.call(window, refresh); - }; - context.frameRequestID = requestAnimFrame.call(window, refresh); - } -} - -export function stopFrameRefreshTimer(context) { - const frameRequestID = context.frameRequestID; - - if (frameRequestID) { - cancelAnimFrame.call(window, frameRequestID); - delete context.frameRequestID; - } -} - -export function stopDataRefreshTimer(context) { - const refreshTimerID = context.refreshTimerID; - - if (refreshTimerID) { - clearInterval(refreshTimerID); - delete context.refreshTimerID; - delete context.refreshInterval; - } -} - -export function startDataRefreshTimer(context, func, interval) { - if (!context.refreshTimerID) { - context.refreshTimerID = setInterval(() => { - const newInterval = call(func); - - if (context.refreshInterval !== newInterval && !isNaN(newInterval)) { - stopDataRefreshTimer(context); - startDataRefreshTimer(context, func, newInterval); - } - }, interval || 0); - context.refreshInterval = interval || 0; - } -} diff --git a/static/js/chartjs-streaming/plugin.streaming.js b/static/js/chartjs-streaming/plugin.streaming.js deleted file mode 100644 index adff208..0000000 --- a/static/js/chartjs-streaming/plugin.streaming.js +++ /dev/null @@ -1,216 +0,0 @@ -import {Chart, DatasetController, defaults, registry} from 'chart.js'; -import {each, noop, getRelativePosition, clipArea, unclipArea} from 'chart.js/helpers'; -import {getAxisMap} from '../helpers/helpers.streaming'; -import {attachChart as annotationAttachChart, detachChart as annotationDetachChart} from '../plugins/plugin.annotation'; -import {update as tooltipUpdate} from '../plugins/plugin.tooltip'; -import {attachChart as zoomAttachChart, detachChart as zoomDetachChart} from '../plugins/plugin.zoom'; -import RealTimeScale from '../scales/scale.realtime'; -import {version} from '../../package.json'; - -defaults.set('transitions', { - quiet: { - animation: { - duration: 0 - } - } -}); - -const transitionKeys = {x: ['x', 'cp1x', 'cp2x'], y: ['y', 'cp1y', 'cp2y']}; - -function update(mode) { - const me = this; - - if (mode === 'quiet') { - each(me.data.datasets, (dataset, datasetIndex) => { - const controller = me.getDatasetMeta(datasetIndex).controller; - - // Set transition mode to 'quiet' - controller._setStyle = function(element, index, _mode, active) { - DatasetController.prototype._setStyle.call(this, element, index, 'quiet', active); - }; - }); - } - - Chart.prototype.update.call(me, mode); - - if (mode === 'quiet') { - each(me.data.datasets, (dataset, datasetIndex) => { - delete me.getDatasetMeta(datasetIndex).controller._setStyle; - }); - } -} - -function render(chart) { - const streaming = chart.$streaming; - - chart.render(); - - if (streaming.lastMouseEvent) { - setTimeout(() => { - const lastMouseEvent = streaming.lastMouseEvent; - if (lastMouseEvent) { - chart._eventHandler(lastMouseEvent); - } - }, 0); - } -} - -export default { - id: 'streaming', - - version, - - beforeInit(chart) { - const streaming = chart.$streaming = chart.$streaming || {render}; - const canvas = streaming.canvas = chart.canvas; - const mouseEventListener = streaming.mouseEventListener = event => { - const pos = getRelativePosition(event, chart); - streaming.lastMouseEvent = { - type: 'mousemove', - chart: chart, - native: event, - x: pos.x, - y: pos.y - }; - }; - - canvas.addEventListener('mousedown', mouseEventListener); - canvas.addEventListener('mouseup', mouseEventListener); - }, - - afterInit(chart) { - chart.update = update; - }, - - beforeUpdate(chart) { - const {scales, elements} = chart.options; - const tooltip = chart.tooltip; - - each(scales, ({type}) => { - if (type === 'realtime') { - // Allow Bézier control to be outside the chart - elements.line.capBezierPoints = false; - } - }); - - if (tooltip) { - tooltip.update = tooltipUpdate; - } - - try { - const plugin = registry.getPlugin('annotation'); - annotationAttachChart(plugin, chart); - } catch (e) { - annotationDetachChart(chart); - } - - try { - const plugin = registry.getPlugin('zoom'); - zoomAttachChart(plugin, chart); - } catch (e) { - zoomDetachChart(chart); - } - }, - - beforeDatasetUpdate(chart, args) { - const {meta, mode} = args; - - if (mode === 'quiet') { - const {controller, $animations} = meta; - - // Skip updating element options if show/hide transition is active - if ($animations && $animations.visible && $animations.visible._active) { - controller.updateElement = noop; - controller.updateSharedOptions = noop; - } - } - }, - - afterDatasetUpdate(chart, args) { - const {meta, mode} = args; - const {data: elements = [], dataset: element, controller} = meta; - - for (let i = 0, ilen = elements.length; i < ilen; ++i) { - elements[i].$streaming = getAxisMap(elements[i], transitionKeys, meta); - } - if (element) { - element.$streaming = getAxisMap(element, transitionKeys, meta); - } - - if (mode === 'quiet') { - delete controller.updateElement; - delete controller.updateSharedOptions; - } - }, - - beforeDatasetDraw(chart, args) { - const {ctx, chartArea, width, height} = chart; - const {xAxisID, yAxisID, controller} = args.meta; - const area = { - left: 0, - top: 0, - right: width, - bottom: height - }; - - if (xAxisID && controller.getScaleForId(xAxisID) instanceof RealTimeScale) { - area.left = chartArea.left; - area.right = chartArea.right; - } - if (yAxisID && controller.getScaleForId(yAxisID) instanceof RealTimeScale) { - area.top = chartArea.top; - area.bottom = chartArea.bottom; - } - clipArea(ctx, area); - }, - - afterDatasetDraw(chart) { - unclipArea(chart.ctx); - }, - - beforeEvent(chart, args) { - const streaming = chart.$streaming; - const event = args.event; - - if (event.type === 'mousemove') { - // Save mousemove event for reuse - streaming.lastMouseEvent = event; - } else if (event.type === 'mouseout') { - // Remove mousemove event - delete streaming.lastMouseEvent; - } - }, - - destroy(chart) { - const {scales, $streaming: streaming, tooltip} = chart; - const {canvas, mouseEventListener} = streaming; - - delete chart.update; - if (tooltip) { - delete tooltip.update; - } - - canvas.removeEventListener('mousedown', mouseEventListener); - canvas.removeEventListener('mouseup', mouseEventListener); - - each(scales, scale => { - if (scale instanceof RealTimeScale) { - scale.destroy(); - } - }); - }, - - defaults: { - duration: 10000, - delay: 0, - frameRate: 30, - refresh: 1000, - onRefresh: null, - pause: false, - ttl: undefined - }, - - descriptors: { - _scriptable: name => name !== 'onRefresh' - } -}; diff --git a/static/js/chartjs-streaming/plugin.zoom.js b/static/js/chartjs-streaming/plugin.zoom.js deleted file mode 100644 index 8e57580..0000000 --- a/static/js/chartjs-streaming/plugin.zoom.js +++ /dev/null @@ -1,125 +0,0 @@ -import {each} from 'chart.js/helpers'; -import {clamp, resolveOption} from '../helpers/helpers.streaming'; - -const chartStates = new WeakMap(); - -function getState(chart) { - let state = chartStates.get(chart); - - if (!state) { - state = {originalScaleOptions: {}}; - chartStates.set(chart, state); - } - return state; -} - -function removeState(chart) { - chartStates.delete(chart); -} - -function storeOriginalScaleOptions(chart) { - const {originalScaleOptions} = getState(chart); - const scales = chart.scales; - - each(scales, scale => { - const id = scale.id; - - if (!originalScaleOptions[id]) { - originalScaleOptions[id] = { - duration: resolveOption(scale, 'duration'), - delay: resolveOption(scale, 'delay') - }; - } - }); - each(originalScaleOptions, (opt, key) => { - if (!scales[key]) { - delete originalScaleOptions[key]; - } - }); - return originalScaleOptions; -} - -function zoomRealTimeScale(scale, zoom, center, limits) { - const {chart, axis} = scale; - const {minDuration = 0, maxDuration = Infinity, minDelay = -Infinity, maxDelay = Infinity} = limits && limits[axis] || {}; - const realtimeOpts = scale.options.realtime; - const duration = resolveOption(scale, 'duration'); - const delay = resolveOption(scale, 'delay'); - const newDuration = clamp(duration * (2 - zoom), minDuration, maxDuration); - let maxPercent, newDelay; - - storeOriginalScaleOptions(chart); - - if (scale.isHorizontal()) { - maxPercent = (scale.right - center.x) / (scale.right - scale.left); - } else { - maxPercent = (scale.bottom - center.y) / (scale.bottom - scale.top); - } - newDelay = delay + maxPercent * (duration - newDuration); - realtimeOpts.duration = newDuration; - realtimeOpts.delay = clamp(newDelay, minDelay, maxDelay); - return newDuration !== scale.max - scale.min; -} - -function panRealTimeScale(scale, delta, limits) { - const {chart, axis} = scale; - const {minDelay = -Infinity, maxDelay = Infinity} = limits && limits[axis] || {}; - const delay = resolveOption(scale, 'delay'); - const newDelay = delay + (scale.getValueForPixel(delta) - scale.getValueForPixel(0)); - - storeOriginalScaleOptions(chart); - - scale.options.realtime.delay = clamp(newDelay, minDelay, maxDelay); - return true; -} - -function resetRealTimeScaleOptions(chart) { - const originalScaleOptions = storeOriginalScaleOptions(chart); - - each(chart.scales, scale => { - const realtimeOptions = scale.options.realtime; - - if (realtimeOptions) { - const original = originalScaleOptions[scale.id]; - - if (original) { - realtimeOptions.duration = original.duration; - realtimeOptions.delay = original.delay; - } else { - delete realtimeOptions.duration; - delete realtimeOptions.delay; - } - } - }); -} - -function initZoomPlugin(plugin) { - plugin.zoomFunctions.realtime = zoomRealTimeScale; - plugin.panFunctions.realtime = panRealTimeScale; -} - -export function attachChart(plugin, chart) { - const streaming = chart.$streaming; - - if (streaming.zoomPlugin !== plugin) { - const resetZoom = streaming.resetZoom = chart.resetZoom; - - initZoomPlugin(plugin); - chart.resetZoom = transition => { - resetRealTimeScaleOptions(chart); - resetZoom(transition); - }; - streaming.zoomPlugin = plugin; - } -} - -export function detachChart(chart) { - const streaming = chart.$streaming; - - if (streaming.zoomPlugin) { - chart.resetZoom = streaming.resetZoom; - removeState(chart); - delete streaming.resetZoom; - delete streaming.zoomPlugin; - } -} diff --git a/static/js/chartjs-streaming/scale.realtime.js b/static/js/chartjs-streaming/scale.realtime.js deleted file mode 100644 index 203be3c..0000000 --- a/static/js/chartjs-streaming/scale.realtime.js +++ /dev/null @@ -1,507 +0,0 @@ -import {defaults, TimeScale} from 'chart.js'; -import {_lookup, callback as call, each, isArray, isFinite, isNumber, noop, clipArea, unclipArea} from 'chart.js/helpers'; -import {resolveOption, startFrameRefreshTimer, stopFrameRefreshTimer, startDataRefreshTimer, stopDataRefreshTimer} from '../helpers/helpers.streaming'; -import {getElements} from '../plugins/plugin.annotation'; - -// Ported from Chart.js 2.8.0 35273ee. -const INTERVALS = { - millisecond: { - common: true, - size: 1, - steps: [1, 2, 5, 10, 20, 50, 100, 250, 500] - }, - second: { - common: true, - size: 1000, - steps: [1, 2, 5, 10, 15, 30] - }, - minute: { - common: true, - size: 60000, - steps: [1, 2, 5, 10, 15, 30] - }, - hour: { - common: true, - size: 3600000, - steps: [1, 2, 3, 6, 12] - }, - day: { - common: true, - size: 86400000, - steps: [1, 2, 5] - }, - week: { - common: false, - size: 604800000, - steps: [1, 2, 3, 4] - }, - month: { - common: true, - size: 2.628e9, - steps: [1, 2, 3] - }, - quarter: { - common: false, - size: 7.884e9, - steps: [1, 2, 3, 4] - }, - year: { - common: true, - size: 3.154e10 - } -}; - -// Ported from Chart.js 2.8.0 35273ee. -const UNITS = Object.keys(INTERVALS); - -// Ported from Chart.js 2.8.0 35273ee. -function determineStepSize(min, max, unit, capacity) { - const range = max - min; - const {size: milliseconds, steps} = INTERVALS[unit]; - let factor; - - if (!steps) { - return Math.ceil(range / (capacity * milliseconds)); - } - - for (let i = 0, ilen = steps.length; i < ilen; ++i) { - factor = steps[i]; - if (Math.ceil(range / (milliseconds * factor)) <= capacity) { - break; - } - } - - return factor; -} - -// Ported from Chart.js 2.8.0 35273ee. -function determineUnitForAutoTicks(minUnit, min, max, capacity) { - const range = max - min; - const ilen = UNITS.length; - - for (let i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) { - const {common, size, steps} = INTERVALS[UNITS[i]]; - const factor = steps ? steps[steps.length - 1] : Number.MAX_SAFE_INTEGER; - - if (common && Math.ceil(range / (factor * size)) <= capacity) { - return UNITS[i]; - } - } - - return UNITS[ilen - 1]; -} - -// Ported from Chart.js 2.8.0 35273ee. -function determineMajorUnit(unit) { - for (let i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) { - if (INTERVALS[UNITS[i]].common) { - return UNITS[i]; - } - } -} - -// Ported from Chart.js 3.2.0 e1404ac. -function addTick(ticks, time, timestamps) { - if (!timestamps) { - ticks[time] = true; - } else if (timestamps.length) { - const {lo, hi} = _lookup(timestamps, time); - const timestamp = timestamps[lo] >= time ? timestamps[lo] : timestamps[hi]; - ticks[timestamp] = true; - } -} - -const datasetPropertyKeys = [ - 'pointBackgroundColor', - 'pointBorderColor', - 'pointBorderWidth', - 'pointRadius', - 'pointRotation', - 'pointStyle', - 'pointHitRadius', - 'pointHoverBackgroundColor', - 'pointHoverBorderColor', - 'pointHoverBorderWidth', - 'pointHoverRadius', - 'backgroundColor', - 'borderColor', - 'borderSkipped', - 'borderWidth', - 'hoverBackgroundColor', - 'hoverBorderColor', - 'hoverBorderWidth', - 'hoverRadius', - 'hitRadius', - 'radius', - 'rotation' -]; - -function clean(scale) { - const {chart, id, max} = scale; - const duration = resolveOption(scale, 'duration'); - const delay = resolveOption(scale, 'delay'); - const ttl = resolveOption(scale, 'ttl'); - const pause = resolveOption(scale, 'pause'); - const min = Date.now() - (isNaN(ttl) ? duration + delay : ttl); - let i, start, count, removalRange; - - // Remove old data - each(chart.data.datasets, (dataset, datasetIndex) => { - const meta = chart.getDatasetMeta(datasetIndex); - const axis = id === meta.xAxisID && 'x' || id === meta.yAxisID && 'y'; - - if (axis) { - const controller = meta.controller; - const data = dataset.data; - const length = data.length; - - if (pause) { - // If the scale is paused, preserve the visible data points - for (i = 0; i < length; ++i) { - const point = controller.getParsed(i); - if (point && !(point[axis] < max)) { - break; - } - } - start = i + 2; - } else { - start = 0; - } - - for (i = start; i < length; ++i) { - const point = controller.getParsed(i); - if (!point || !(point[axis] <= min)) { - break; - } - } - count = i - start; - if (isNaN(ttl)) { - // Keep the last two data points outside the range not to affect the existing bezier curve - count = Math.max(count - 2, 0); - } - - data.splice(start, count); - each(datasetPropertyKeys, key => { - if (isArray(dataset[key])) { - dataset[key].splice(start, count); - } - }); - each(dataset.datalabels, value => { - if (isArray(value)) { - value.splice(start, count); - } - }); - if (typeof data[0] !== 'object') { - removalRange = { - start: start, - count: count - }; - } - - each(chart._active, (item, index) => { - if (item.datasetIndex === datasetIndex && item.index >= start) { - if (item.index >= start + count) { - item.index -= count; - } else { - chart._active.splice(index, 1); - } - } - }, null, true); - } - }); - if (removalRange) { - chart.data.labels.splice(removalRange.start, removalRange.count); - } -} - -function transition(element, id, translate) { - const animations = element.$animations || {}; - - each(element.$streaming, (item, key) => { - if (item.axisId === id) { - const delta = item.reverse ? -translate : translate; - const animation = animations[key]; - - if (isFinite(element[key])) { - element[key] -= delta; - } - if (animation) { - animation._from -= delta; - animation._to -= delta; - } - } - }); -} - -function scroll(scale) { - const {chart, id, $realtime: realtime} = scale; - const duration = resolveOption(scale, 'duration'); - const delay = resolveOption(scale, 'delay'); - const isHorizontal = scale.isHorizontal(); - const length = isHorizontal ? scale.width : scale.height; - const now = Date.now(); - const tooltip = chart.tooltip; - const annotations = getElements(chart); - let offset = length * (now - realtime.head) / duration; - - if (isHorizontal === !!scale.options.reverse) { - offset = -offset; - } - - // Shift all the elements leftward or downward - each(chart.data.datasets, (dataset, datasetIndex) => { - const meta = chart.getDatasetMeta(datasetIndex); - const {data: elements = [], dataset: element} = meta; - - for (let i = 0, ilen = elements.length; i < ilen; ++i) { - transition(elements[i], id, offset); - } - if (element) { - transition(element, id, offset); - delete element._path; - } - }); - - // Shift all the annotation elements leftward or downward - for (let i = 0, ilen = annotations.length; i < ilen; ++i) { - transition(annotations[i], id, offset); - } - - // Shift tooltip leftward or downward - if (tooltip) { - transition(tooltip, id, offset); - } - - scale.max = now - delay; - scale.min = scale.max - duration; - - realtime.head = now; -} - -export default class RealTimeScale extends TimeScale { - - constructor(props) { - super(props); - this.$realtime = this.$realtime || {}; - } - - init(scaleOpts, opts) { - const me = this; - - super.init(scaleOpts, opts); - startDataRefreshTimer(me.$realtime, () => { - const chart = me.chart; - const onRefresh = resolveOption(me, 'onRefresh'); - - call(onRefresh, [chart], me); - clean(me); - chart.update('quiet'); - return resolveOption(me, 'refresh'); - }); - } - - update(maxWidth, maxHeight, margins) { - const me = this; - const {$realtime: realtime, options} = me; - const {bounds, offset, ticks: ticksOpts} = options; - const {autoSkip, source, major: majorTicksOpts} = ticksOpts; - const majorEnabled = majorTicksOpts.enabled; - - if (resolveOption(me, 'pause')) { - stopFrameRefreshTimer(realtime); - } else { - if (!realtime.frameRequestID) { - realtime.head = Date.now(); - } - startFrameRefreshTimer(realtime, () => { - const chart = me.chart; - const streaming = chart.$streaming; - - scroll(me); - if (streaming) { - call(streaming.render, [chart]); - } - return resolveOption(me, 'frameRate'); - }); - } - - options.bounds = undefined; - options.offset = false; - ticksOpts.autoSkip = false; - ticksOpts.source = source === 'auto' ? '' : source; - majorTicksOpts.enabled = true; - - super.update(maxWidth, maxHeight, margins); - - options.bounds = bounds; - options.offset = offset; - ticksOpts.autoSkip = autoSkip; - ticksOpts.source = source; - majorTicksOpts.enabled = majorEnabled; - } - - buildTicks() { - const me = this; - const duration = resolveOption(me, 'duration'); - const delay = resolveOption(me, 'delay'); - const max = me.$realtime.head - delay; - const min = max - duration; - const maxArray = [1e15, max]; - const minArray = [-1e15, min]; - - Object.defineProperty(me, 'min', { - get: () => minArray.shift(), - set: noop - }); - Object.defineProperty(me, 'max', { - get: () => maxArray.shift(), - set: noop - }); - - const ticks = super.buildTicks(); - - delete me.min; - delete me.max; - me.min = min; - me.max = max; - - return ticks; - } - - calculateLabelRotation() { - const ticksOpts = this.options.ticks; - const maxRotation = ticksOpts.maxRotation; - - ticksOpts.maxRotation = ticksOpts.minRotation || 0; - super.calculateLabelRotation(); - ticksOpts.maxRotation = maxRotation; - } - - fit() { - const me = this; - const options = me.options; - - super.fit(); - - if (options.ticks.display && options.display && me.isHorizontal()) { - me.paddingLeft = 3; - me.paddingRight = 3; - me._handleMargins(); - } - } - - draw(chartArea) { - const me = this; - const {chart, ctx} = me; - const area = me.isHorizontal() ? - { - left: chartArea.left, - top: 0, - right: chartArea.right, - bottom: chart.height - } : { - left: 0, - top: chartArea.top, - right: chart.width, - bottom: chartArea.bottom - }; - - me._gridLineItems = null; - me._labelItems = null; - - // Clip and draw the scale - clipArea(ctx, area); - super.draw(chartArea); - unclipArea(ctx); - } - - destroy() { - const realtime = this.$realtime; - - stopFrameRefreshTimer(realtime); - stopDataRefreshTimer(realtime); - } - - _generate() { - const me = this; - const adapter = me._adapter; - const duration = resolveOption(me, 'duration'); - const delay = resolveOption(me, 'delay'); - const refresh = resolveOption(me, 'refresh'); - const max = me.$realtime.head - delay; - const min = max - duration; - const capacity = me._getLabelCapacity(min); - const {time: timeOpts, ticks: ticksOpts} = me.options; - const minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, capacity); - const major = determineMajorUnit(minor); - const stepSize = timeOpts.stepSize || determineStepSize(min, max, minor, capacity); - const weekday = minor === 'week' ? timeOpts.isoWeekday : false; - const majorTicksEnabled = ticksOpts.major.enabled; - const hasWeekday = isNumber(weekday) || weekday === true; - const interval = INTERVALS[minor]; - const ticks = {}; - let first = min; - let time, count; - - // For 'week' unit, handle the first day of week option - if (hasWeekday) { - first = +adapter.startOf(first, 'isoWeek', weekday); - } - - // Align first ticks on unit - first = +adapter.startOf(first, hasWeekday ? 'day' : minor); - - // Prevent browser from freezing in case user options request millions of milliseconds - if (adapter.diff(max, min, minor) > 100000 * stepSize) { - throw new Error(min + ' and ' + max + ' are too far apart with stepSize of ' + stepSize + ' ' + minor); - } - - time = first; - - if (majorTicksEnabled && major && !hasWeekday && !timeOpts.round) { - // Align the first tick on the previous `minor` unit aligned on the `major` unit: - // we first aligned time on the previous `major` unit then add the number of full - // stepSize there is between first and the previous major time. - time = +adapter.startOf(time, major); - time = +adapter.add(time, ~~((first - time) / (interval.size * stepSize)) * stepSize, minor); - } - - const timestamps = ticksOpts.source === 'data' && me.getDataTimestamps(); - for (count = 0; time < max + refresh; time = +adapter.add(time, stepSize, minor), count++) { - addTick(ticks, time, timestamps); - } - - if (time === max + refresh || count === 1) { - addTick(ticks, time, timestamps); - } - - return Object.keys(ticks).sort((a, b) => a - b).map(x => +x); - } -} - -RealTimeScale.id = 'realtime'; - -RealTimeScale.defaults = { - bounds: 'data', - adapters: {}, - time: { - parser: false, // false == a pattern string from or a custom callback that converts its argument to a timestamp - unit: false, // false == automatic or override with week, month, year, etc. - round: false, // none, or override with week, month, year, etc. - isoWeekday: false, // override week start day - see http://momentjs.com/docs/#/get-set/iso-weekday/ - minUnit: 'millisecond', - displayFormats: {} - }, - realtime: {}, - ticks: { - autoSkip: false, - source: 'auto', - major: { - enabled: true - } - } -}; - -defaults.describe('scale.realtime', { - _scriptable: name => name !== 'onRefresh' -}); diff --git a/static/js/config_editor.js b/static/js/config_editor.js deleted file mode 100644 index e44b16b..0000000 --- a/static/js/config_editor.js +++ /dev/null @@ -1,541 +0,0 @@ -/** - * 🧩 Dynamic JSON Config Editor - * Construye formularios en base a JSON Schema y llama a endpoints /api/config - */ - -(function () { - let schemasIndex = []; - let currentSchemaId = null; - let currentData = null; - - document.addEventListener('DOMContentLoaded', () => { - const tabBtn = document.querySelector('.tab-btn[data-tab="config-editor"]'); - if (!tabBtn) return; - - // Cargar esquemas cuando se entra al tab - tabBtn.addEventListener('click', ensureSchemasLoadedOnce); - - // Listeners de controles básicos - const saveBtn = document.getElementById('btn-save-config'); - if (saveBtn) saveBtn.addEventListener('click', onSave); - - const exportBtn = document.getElementById('btn-export-config'); - if (exportBtn) exportBtn.addEventListener('click', onExport); - - const importInput = document.getElementById('import-file'); - if (importInput) importInput.addEventListener('change', onImport); - }); - - async function ensureSchemasLoadedOnce() { - if (schemasIndex.length > 0) return; - try { - const res = await fetch('/api/config/schemas'); - const data = await res.json(); - if (!data.success) throw new Error(data.error || 'Failed to list schemas'); - schemasIndex = data.schemas || []; - populateSchemaSelector(); - } catch (e) { - showMessage(`Error loading schemas: ${e}`, 'error'); - } - } - - function populateSchemaSelector() { - const selector = document.getElementById('schema-selector'); - if (!selector) return; - selector.innerHTML = ''; - - for (const s of schemasIndex) { - const opt = document.createElement('option'); - opt.value = s.id; - opt.textContent = `${iconForSchema(s.id)} ${s.title || s.id}`; - selector.appendChild(opt); - } - - selector.addEventListener('change', () => loadConfigAndSchema(selector.value)); - if (schemasIndex.length > 0) { - selector.value = schemasIndex[0].id; - loadConfigAndSchema(selector.value); - } - } - - function iconForSchema(id) { - if (id === 'plc') return '⚙️'; - if (id === 'datasets') return '📊'; - if (id === 'plots') return '📈'; - return '🧩'; - } - - async function loadConfigAndSchema(schemaId) { - currentSchemaId = schemaId; - const container = document.getElementById('config-form-container'); - if (container) container.innerHTML = 'Loading...'; - - try { - const [schemaRes, dataRes] = await Promise.all([ - fetch(`/api/config/schema/${schemaId}`), - fetch(`/api/config/${schemaId}`) - ]); - - const schemaData = await schemaRes.json(); - const configData = await dataRes.json(); - - if (!schemaData.success) throw new Error(schemaData.error || 'Schema error'); - if (!configData.success) throw new Error(configData.error || 'Data error'); - - currentData = configData.data; - - if (container) container.innerHTML = ''; - - // Prefer JSONForm if available for a simple form UI - if (window.$ && window._ && typeof $.fn.jsonForm === 'function') { - const formEl = document.createElement('form'); - formEl.id = 'jsonform-form'; - container.appendChild(formEl); - - const formDef = ["*", { "type": "submit", "title": "Save" }]; - - $(formEl).jsonForm({ - schema: schemaData.schema, - form: formDef, - value: currentData, - onSubmit: function (errors, values) { - if (errors) { - showMessage('Validation errors in form', 'error'); - return false; - } - doSave(values); - return false; - } - }); - - const saveBtn = document.getElementById('btn-save-config'); - if (saveBtn) { - saveBtn.onclick = () => { - const f = document.getElementById('jsonform-form'); - if (f) f.requestSubmit ? f.requestSubmit() : f.dispatchEvent(new Event('submit', { cancelable: true })); - }; - } - } else { - // Fallback: minimal manual renderer based on schema - renderForm(container, schemaData.schema, currentData); - } - } catch (e) { - if (container) container.innerHTML = ''; - showMessage(`Error loading editor: ${e}`, 'error'); - } - } - - // Renderizado muy simple basado en schema: soporta object, string, number, integer, boolean, array básica - function renderForm(container, schema, data) { - if (!container) return; - container.innerHTML = ''; - - const form = document.createElement('div'); - form.className = 'config-editor-form'; - - if (schema.type === 'object' && schema.properties) { - for (const [key, propSchema] of Object.entries(schema.properties)) { - const value = data ? data[key] : undefined; - const field = renderField(key, propSchema, value, [key]); - if (field) form.appendChild(field); - } - } else { - form.textContent = 'Unsupported schema root.'; - } - - container.appendChild(form); - } - - function renderField(label, propSchema, value, path) { - const wrapper = document.createElement('div'); - wrapper.className = 'form-group'; - - const title = document.createElement('label'); - title.textContent = propSchema.title || label; - wrapper.appendChild(title); - - // Optional description/help text - if (propSchema.description) { - const help = document.createElement('small'); - help.textContent = propSchema.description; - help.style.display = 'block'; - help.style.color = 'var(--pico-muted-color)'; - help.style.marginTop = '-0.25rem'; - help.style.marginBottom = '0.25rem'; - wrapper.appendChild(help); - } - - const type = Array.isArray(propSchema.type) ? propSchema.type : [propSchema.type]; - - // Objetos - if (type.includes('object')) { - const inner = document.createElement('div'); - inner.className = 'object-group'; - - // Caso 1: propiedades conocidas - if (propSchema.properties) { - for (const [k, s] of Object.entries(propSchema.properties)) { - const v = value ? value[k] : undefined; - const child = renderField(k, s, v, path.concat(k)); - if (child) inner.appendChild(child); - } - wrapper.appendChild(inner); - return wrapper; - } - - // Caso 2: additionalProperties -> colección dinámica (key -> objeto) - if (propSchema.additionalProperties && typeof propSchema.additionalProperties === 'object') { - const entries = (value && typeof value === 'object') ? Object.entries(value) : []; - const list = document.createElement('div'); - list.className = 'dynamic-object-list'; - - function renderEntries() { - list.innerHTML = ''; - for (const [entryKey, entryVal] of entries) { - const row = document.createElement('div'); - row.className = 'dynamic-object-row'; - - const keyInput = document.createElement('input'); - keyInput.type = 'text'; - keyInput.value = entryKey; - keyInput.title = 'Key'; - - let currentKey = entryKey; - keyInput.addEventListener('change', () => { - const newKey = keyInput.value.trim(); - if (!newKey || newKey === currentKey) return; - // Renombrar clave conservando valor - const parentObj = getPathObject(path, true); - if (parentObj[newKey] !== undefined) { - showMessage('Key already exists', 'error'); - keyInput.value = currentKey; - return; - } - parentObj[newKey] = parentObj[currentKey]; - delete parentObj[currentKey]; - currentKey = newKey; - updateEntriesFromObject(parentObj); - setPathValue(path, parentObj); - renderEntries(); - }); - - const delBtn = document.createElement('button'); - delBtn.type = 'button'; - delBtn.className = 'secondary'; - delBtn.textContent = '🗑️'; - delBtn.addEventListener('click', () => { - const parentObj = getPathObject(path, true); - delete parentObj[currentKey]; - updateEntriesFromObject(parentObj); - setPathValue(path, parentObj); - renderEntries(); - }); - - const valueContainer = document.createElement('div'); - valueContainer.className = 'dynamic-object-value'; - const child = renderField(currentKey, propSchema.additionalProperties, entryVal, path.concat(currentKey)); - - row.appendChild(keyInput); - row.appendChild(delBtn); - if (child) valueContainer.appendChild(child); - row.appendChild(valueContainer); - list.appendChild(row); - } - } - - function updateEntriesFromObject(parentObj) { - const arr = Object.entries(parentObj); - entries.length = 0; - arr.forEach(e => entries.push(e)); - } - - renderEntries(); - - const addWrap = document.createElement('div'); - const addBtn = document.createElement('button'); - addBtn.type = 'button'; - addBtn.className = 'outline'; - addBtn.textContent = '➕ Add'; - addBtn.addEventListener('click', () => { - const key = prompt('Enter key name'); - if (!key) return; - const parentObj = getPathObject(path, true); - if (parentObj[key] !== undefined) { - showMessage('Key already exists', 'error'); - return; - } - parentObj[key] = defaultForSchema(propSchema.additionalProperties); - setPathValue(path, parentObj); - updateEntriesFromObject(parentObj); - renderEntries(); - }); - addWrap.appendChild(addBtn); - - inner.appendChild(list); - inner.appendChild(addWrap); - wrapper.appendChild(inner); - return wrapper; - } - - // Objeto sin propiedades definidas - const note = document.createElement('div'); - note.textContent = '(object)'; - wrapper.appendChild(note); - return wrapper; - } - - // Boolean con toggle opcional - if (type.includes('boolean')) { - const toggle = document.createElement('button'); - toggle.type = 'button'; - const labels = propSchema?.['x-ui']?.toggleLabels || ['On', 'Off']; - let current = !!value; - toggle.textContent = current ? labels[0] : labels[1]; - toggle.className = current ? 'outline' : 'secondary'; - toggle.addEventListener('click', () => { - current = !current; - setPathValue(path, current); - toggle.textContent = current ? labels[0] : labels[1]; - toggle.className = current ? 'outline' : 'secondary'; - }); - wrapper.appendChild(toggle); - return wrapper; - } - - // Enum (select) o string simple - if (propSchema.enum) { - const select = document.createElement('select'); - for (const opt of propSchema.enum) { - const o = document.createElement('option'); - o.value = opt; - o.textContent = String(opt).toUpperCase(); - if (value === opt) o.selected = true; - select.appendChild(o); - } - select.addEventListener('change', () => setPathValue(path, select.value)); - wrapper.appendChild(select); - return wrapper; - } - - if (type.includes('number') || type.includes('integer')) { - const input = document.createElement('input'); - input.type = 'number'; - if (typeof value === 'number') input.value = String(value); - if (typeof propSchema.minimum !== 'undefined') input.min = String(propSchema.minimum); - if (typeof propSchema.maximum !== 'undefined') input.max = String(propSchema.maximum); - if (propSchema.step) input.step = String(propSchema.step); - input.addEventListener('input', () => { - const v = input.value === '' ? null : (type.includes('integer') ? parseInt(input.value) : parseFloat(input.value)); - setPathValue(path, v); - }); - wrapper.appendChild(input); - return wrapper; - } - - if (type.includes('array')) { - const arrWrap = document.createElement('div'); - arrWrap.className = 'array-group'; - - const list = document.createElement('div'); - const arr = Array.isArray(value) ? value : []; - - function renderItems() { - list.innerHTML = ''; - arr.forEach((itemVal, idx) => { - const row = document.createElement('div'); - row.className = 'array-item-row'; - // Soporta items string simples por ahora - const itemInput = document.createElement('input'); - itemInput.type = 'text'; - itemInput.value = itemVal; - itemInput.addEventListener('input', () => { - arr[idx] = itemInput.value; - setPathValue(path, arr.slice()); - }); - const delBtn = document.createElement('button'); - delBtn.type = 'button'; - delBtn.className = 'secondary'; - delBtn.textContent = '🗑️'; - delBtn.addEventListener('click', () => { - arr.splice(idx, 1); - setPathValue(path, arr.slice()); - renderItems(); - }); - row.appendChild(itemInput); - row.appendChild(delBtn); - list.appendChild(row); - }); - } - - renderItems(); - - const addBtn = document.createElement('button'); - addBtn.type = 'button'; - addBtn.className = 'outline'; - addBtn.textContent = '➕ Add'; - addBtn.addEventListener('click', () => { - arr.push(''); - setPathValue(path, arr.slice()); - renderItems(); - }); - - arrWrap.appendChild(list); - arrWrap.appendChild(addBtn); - wrapper.appendChild(arrWrap); - return wrapper; - } - - // Fallback: string - const input = document.createElement('input'); - input.type = 'text'; - input.value = value ?? ''; - input.placeholder = propSchema.placeholder || ''; - input.addEventListener('input', () => setPathValue(path, input.value)); - wrapper.appendChild(input); - return wrapper; - } - - function setPathValue(path, v) { - if (!currentData) currentData = {}; - let cursor = currentData; - for (let i = 0; i < path.length - 1; i++) { - const key = path[i]; - if (!cursor[key] || typeof cursor[key] !== 'object') cursor[key] = {}; - cursor = cursor[key]; - } - cursor[path[path.length - 1]] = v; - } - - function getPathObject(path, createIfMissing = false) { - if (!currentData) currentData = {}; - let cursor = currentData; - for (let i = 0; i < path.length; i++) { - const key = path[i]; - if (i === path.length - 1) { - if (typeof cursor[key] !== 'object' || cursor[key] === null) { - if (createIfMissing) cursor[key] = {}; - else return {}; - } - return cursor[key]; - } - if (!cursor[key] || typeof cursor[key] !== 'object') { - if (createIfMissing) cursor[key] = {}; - else return {}; - } - cursor = cursor[key]; - } - return {}; - } - - function defaultForSchema(schema) { - const t = Array.isArray(schema.type) ? schema.type[0] : schema.type; - if (t === 'object') { - const obj = {}; - if (schema.properties) { - for (const [k, s] of Object.entries(schema.properties)) { - if (typeof s.default !== 'undefined') obj[k] = s.default; - else obj[k] = defaultForSchema(s); - } - } - return obj; - } - if (t === 'array') return []; - if (t === 'boolean') return !!schema.default; - if (t === 'number' || t === 'integer') return typeof schema.default !== 'undefined' ? schema.default : 0; - if (t === 'string') return schema.default || ''; - return null; - } - - async function doSave(payload) { - if (!currentSchemaId) return; - try { - const res = await fetch(`/api/config/${currentSchemaId}`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(payload) - }); - const result = await res.json(); - if (result.success) showMessage('Configuration saved successfully', 'success'); - else showMessage(result.error || 'Failed to save configuration', 'error'); - } catch (e) { - showMessage(`Error saving configuration: ${e}`, 'error'); - } - } - - async function onSave() { - if (!currentSchemaId) return; - try { - // If JSONForm exists, its Save is already wired; fall back to currentData - let payload = currentData || {}; - if (window.__jsonEditorInstance && typeof window.__jsonEditorInstance.get === 'function') { - // Legacy safety; should not happen because we no longer render JSONEditor - payload = window.__jsonEditorInstance.get(); - } - await doSave(payload); - } catch (e) { - showMessage(`Error saving configuration: ${e}`, 'error'); - } - } - - async function onExport() { - if (!currentSchemaId) return; - try { - let val = currentData || {}; - // Prefer currentData; JSONForm updates are applied on submit - if (window.__jsonEditorInstance && typeof window.__jsonEditorInstance.get === 'function') { - val = window.__jsonEditorInstance.get(); - } - const blob = new Blob([JSON.stringify(val, null, 2)], { type: 'application/json' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = `${currentSchemaId}_export.json`; - document.body.appendChild(a); - a.click(); - setTimeout(() => { - document.body.removeChild(a); - URL.revokeObjectURL(url); - }, 0); - } catch (e) { - showMessage(`Error exporting configuration: ${e}`, 'error'); - } - } - - async function onImport(evt) { - const file = evt.target.files && evt.target.files[0]; - if (!file || !currentSchemaId) return; - try { - const text = await file.text(); - const json = JSON.parse(text); - currentData = json; - const formEl = document.getElementById('jsonform-form'); - if (formEl && window.$ && typeof $(formEl).jsonForm === 'function') { - const schemaRes = await fetch(`/api/config/schema/${currentSchemaId}`); - const schemaData = await schemaRes.json(); - if (!schemaData.success) throw new Error(schemaData.error || 'Schema error'); - $(formEl).jsonForm({ - schema: schemaData.schema, - form: ["*", { "type": "submit", "title": "Save" }], - value: currentData, - onSubmit: function (errors, values) { - if (errors) return showMessage('Validation errors in form', 'error'); - doSave(values); - return false; - } - }); - } else { - const res = await fetch(`/api/config/schema/${currentSchemaId}`); - const schemaData = await res.json(); - if (!schemaData.success) throw new Error(schemaData.error || 'Schema error'); - renderForm(document.getElementById('config-form-container'), schemaData.schema, currentData); - } - showMessage('JSON imported (not saved yet)', 'info'); - } catch (e) { - showMessage(`Invalid JSON: ${e}`, 'error'); - } finally { - evt.target.value = ''; - } - } -})(); - - diff --git a/static/js/csv.js b/static/js/csv.js deleted file mode 100644 index 50e1314..0000000 --- a/static/js/csv.js +++ /dev/null @@ -1,170 +0,0 @@ -/** - * Gestión de la configuración CSV y operaciones relacionadas - */ - -// Cargar configuración CSV -function loadCsvConfig() { - fetch('/api/csv/config') - .then(response => response.json()) - .then(data => { - if (data.success) { - const config = data.config; - - // Actualizar elementos de visualización - document.getElementById('csv-directory-path').textContent = config.current_directory || 'N/A'; - document.getElementById('csv-rotation-enabled').textContent = config.rotation_enabled ? '✅ Yes' : '❌ No'; - document.getElementById('csv-max-size').textContent = config.max_size_mb ? `${config.max_size_mb} MB` : 'No limit'; - document.getElementById('csv-max-days').textContent = config.max_days ? `${config.max_days} days` : 'No limit'; - document.getElementById('csv-max-hours').textContent = config.max_hours ? `${config.max_hours} hours` : 'No limit'; - document.getElementById('csv-cleanup-interval').textContent = `${config.cleanup_interval_hours} hours`; - - // Actualizar campos del formulario - document.getElementById('records-directory').value = config.records_directory || ''; - document.getElementById('rotation-enabled').checked = config.rotation_enabled || false; - document.getElementById('max-size-mb').value = config.max_size_mb || ''; - document.getElementById('max-days').value = config.max_days || ''; - document.getElementById('max-hours').value = config.max_hours || ''; - document.getElementById('cleanup-interval').value = config.cleanup_interval_hours || 24; - - // Cargar información del directorio - loadCsvDirectoryInfo(); - } else { - showMessage('Error loading CSV configuration: ' + data.message, 'error'); - } - }) - .catch(error => { - showMessage('Error loading CSV configuration', 'error'); - }); -} - -// Cargar información del directorio CSV -function loadCsvDirectoryInfo() { - fetch('/api/csv/directory/info') - .then(response => response.json()) - .then(data => { - if (data.success) { - const info = data.info; - const statsDiv = document.getElementById('directory-stats'); - - let html = ` -
- 📁 Directory: - ${info.base_directory} -
-
- 📊 Total Files: - ${info.total_files} -
-
- 💾 Total Size: - ${info.total_size_mb} MB -
- `; - - if (info.oldest_file) { - html += ` -
- 📅 Oldest File: - ${new Date(info.oldest_file).toLocaleString()} -
- `; - } - - if (info.newest_file) { - html += ` -
- 🆕 Newest File: - ${new Date(info.newest_file).toLocaleString()} -
- `; - } - - if (info.day_folders && info.day_folders.length > 0) { - html += '

📂 Day Folders:

'; - info.day_folders.forEach(folder => { - html += ` -
- ${folder.name} - ${folder.files} files, ${folder.size_mb} MB -
- `; - }); - } - - statsDiv.innerHTML = html; - } - }) - .catch(error => { - document.getElementById('directory-stats').innerHTML = '

Error loading directory information

'; - }); -} - -// Ejecutar limpieza manual -function triggerManualCleanup() { - if (!confirm('¿Estás seguro de que quieres ejecutar la limpieza manual? Esto eliminará archivos antiguos según la configuración actual.')) { - return; - } - - fetch('/api/csv/cleanup', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - } - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - showMessage('Limpieza ejecutada correctamente', 'success'); - loadCsvDirectoryInfo(); // Recargar información del directorio - } else { - showMessage('Error en la limpieza: ' + data.message, 'error'); - } - }) - .catch(error => { - showMessage('Error ejecutando la limpieza', 'error'); - }); -} - -// Inicializar listeners para la configuración CSV -function initCsvListeners() { - // Manejar envío del formulario de configuración CSV - document.getElementById('csv-config-form').addEventListener('submit', function (e) { - e.preventDefault(); - - const formData = new FormData(e.target); - const configData = {}; - - // Convertir datos del formulario a objeto, manejando valores vacíos - for (let [key, value] of formData.entries()) { - if (key === 'rotation_enabled') { - configData[key] = document.getElementById('rotation-enabled').checked; - } else if (value.trim() === '') { - configData[key] = null; - } else if (key.includes('max_') || key.includes('cleanup_interval')) { - configData[key] = parseFloat(value) || null; - } else { - configData[key] = value.trim(); - } - } - - fetch('/api/csv/config', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(configData) - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - showMessage('Configuración CSV actualizada correctamente', 'success'); - loadCsvConfig(); // Recargar para mostrar valores actualizados - } else { - showMessage('Error actualizando configuración CSV: ' + data.message, 'error'); - } - }) - .catch(error => { - showMessage('Error actualizando configuración CSV', 'error'); - }); - }); -} \ No newline at end of file diff --git a/static/js/datasets.js b/static/js/datasets.js deleted file mode 100644 index bdf5d98..0000000 --- a/static/js/datasets.js +++ /dev/null @@ -1,519 +0,0 @@ -/** - * Gestión de datasets y variables asociadas - */ - -// Variables de gestión de datasets -let currentDatasets = {}; -let currentDatasetId = null; - -// Cargar todos los datasets desde API -window.loadDatasets = function () { - fetch('/api/datasets') - .then(response => response.json()) - .then(data => { - if (data.success) { - currentDatasets = data.datasets; - currentDatasetId = data.current_dataset_id; - updateDatasetSelector(); - updateDatasetInfo(); - } - }) - .catch(error => { - console.error('Error loading datasets:', error); - showMessage('Error loading datasets', 'error'); - }); -} - -// Actualizar el selector de datasets -function updateDatasetSelector() { - const selector = document.getElementById('dataset-selector'); - selector.innerHTML = ''; - - Object.keys(currentDatasets).forEach(datasetId => { - const dataset = currentDatasets[datasetId]; - const option = document.createElement('option'); - option.value = datasetId; - option.textContent = `${dataset.name} (${dataset.prefix})`; - if (datasetId === currentDatasetId) { - option.selected = true; - } - selector.appendChild(option); - }); -} - -// Actualizar información del dataset -function updateDatasetInfo() { - const statusBar = document.getElementById('dataset-status-bar'); - const variablesManagement = document.getElementById('variables-management'); - const noDatasetMessage = document.getElementById('no-dataset-message'); - - if (currentDatasetId && currentDatasets[currentDatasetId]) { - const dataset = currentDatasets[currentDatasetId]; - - // Mostrar info del dataset en la barra de estado - document.getElementById('dataset-name').textContent = dataset.name; - document.getElementById('dataset-prefix').textContent = dataset.prefix; - document.getElementById('dataset-sampling').textContent = - dataset.sampling_interval ? `${dataset.sampling_interval}s` : 'Global interval'; - document.getElementById('dataset-var-count').textContent = Object.keys(dataset.variables).length; - document.getElementById('dataset-stream-count').textContent = dataset.streaming_variables.length; - - // Actualizar estado del dataset - const statusSpan = document.getElementById('dataset-status'); - const isActive = dataset.enabled; - statusSpan.textContent = isActive ? '🟢 Active' : '⭕ Inactive'; - statusSpan.className = `status-item ${isActive ? 'status-active' : 'status-inactive'}`; - - // Actualizar botones de acción - document.getElementById('activate-dataset-btn').style.display = isActive ? 'none' : 'inline-block'; - document.getElementById('deactivate-dataset-btn').style.display = isActive ? 'inline-block' : 'none'; - - // Mostrar secciones - statusBar.style.display = 'block'; - variablesManagement.style.display = 'block'; - noDatasetMessage.style.display = 'none'; - - // Cargar variables para este dataset - loadDatasetVariables(currentDatasetId); - } else { - statusBar.style.display = 'none'; - variablesManagement.style.display = 'none'; - noDatasetMessage.style.display = 'block'; - } -} - -// Cargar variables para un dataset específico -function loadDatasetVariables(datasetId) { - if (!datasetId || !currentDatasets[datasetId]) { - // Limpiar la tabla si no hay dataset válido - document.getElementById('variables-tbody').innerHTML = ''; - return; - } - - const dataset = currentDatasets[datasetId]; - const variables = dataset.variables || {}; - const streamingVars = dataset.streaming_variables || []; - const tbody = document.getElementById('variables-tbody'); - - // Limpiar filas existentes - tbody.innerHTML = ''; - - // Añadir una fila para cada variable - Object.keys(variables).forEach(varName => { - const variable = variables[varName]; - const row = document.createElement('tr'); - - // Formatear visualización del área de memoria - let memoryAreaDisplay = ''; - if (variable.area === 'db') { - memoryAreaDisplay = `DB${variable.db || 'N/A'}.${variable.offset}`; - } else if (variable.area === 'mw' || variable.area === 'm') { - memoryAreaDisplay = `MW${variable.offset}`; - } else if (variable.area === 'pew' || variable.area === 'pe') { - memoryAreaDisplay = `PEW${variable.offset}`; - } else if (variable.area === 'paw' || variable.area === 'pa') { - memoryAreaDisplay = `PAW${variable.offset}`; - } else if (variable.area === 'e') { - memoryAreaDisplay = `E${variable.offset}.${variable.bit}`; - } else if (variable.area === 'a') { - memoryAreaDisplay = `A${variable.offset}.${variable.bit}`; - } else if (variable.area === 'mb') { - memoryAreaDisplay = `M${variable.offset}.${variable.bit}`; - } else { - memoryAreaDisplay = `${variable.area.toUpperCase()}${variable.offset}`; - } - - // Comprobar si la variable está en la lista de streaming - const isStreaming = streamingVars.includes(varName); - - row.innerHTML = ` - ${varName} - ${memoryAreaDisplay} - ${variable.offset} - ${variable.type.toUpperCase()} - - -- - - - - - - - - - `; - - tbody.appendChild(row); - }); -} - -// Inicializar listeners de eventos para datasets -function initDatasetListeners() { - // Cambio de selector de dataset - document.getElementById('dataset-selector').addEventListener('change', function () { - const selectedDatasetId = this.value; - if (selectedDatasetId) { - // Detener streaming de variables actual si está activo - if (isStreamingVariables) { - stopVariableStreaming(); - } - - // Establecer como dataset actual - fetch('/api/datasets/current', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ dataset_id: selectedDatasetId }) - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - currentDatasetId = selectedDatasetId; - // Recargar datasets para obtener datos frescos, luego actualizar info - loadDatasets(); - - // Actualizar texto del botón de streaming - const toggleBtn = document.getElementById('toggle-streaming-btn'); - if (toggleBtn) { - toggleBtn.innerHTML = '▶️ Start Live Streaming'; - } - - // Auto-refrescar valores para el nuevo dataset - autoStartLiveDisplay(); - } else { - showMessage(data.message, 'error'); - } - }) - .catch(error => { - showMessage('Error setting current dataset', 'error'); - }); - } else { - // Detener streaming de variables si está activo - if (isStreamingVariables) { - stopVariableStreaming(); - } - - currentDatasetId = null; - updateDatasetInfo(); - // Limpiar valores cuando no hay dataset seleccionado - clearVariableValues(); - } - }); - - // Botón de nuevo dataset - document.getElementById('new-dataset-btn').addEventListener('click', function () { - document.getElementById('dataset-modal').style.display = 'block'; - }); - - // Cerrar modal de dataset - document.getElementById('close-dataset-modal').addEventListener('click', function () { - document.getElementById('dataset-modal').style.display = 'none'; - }); - - document.getElementById('cancel-dataset-btn').addEventListener('click', function () { - document.getElementById('dataset-modal').style.display = 'none'; - }); - - // Crear nuevo dataset - document.getElementById('dataset-form').addEventListener('submit', function (e) { - e.preventDefault(); - - const data = { - dataset_id: document.getElementById('dataset-id').value.trim(), - name: document.getElementById('dataset-name-input').value.trim(), - prefix: document.getElementById('dataset-prefix-input').value.trim(), - sampling_interval: document.getElementById('dataset-sampling-input').value || null - }; - - if (data.sampling_interval) { - data.sampling_interval = parseFloat(data.sampling_interval); - } - - fetch('/api/datasets', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(data) - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - showMessage(data.message, 'success'); - document.getElementById('dataset-modal').style.display = 'none'; - document.getElementById('dataset-form').reset(); - loadDatasets(); - } else { - showMessage(data.message, 'error'); - } - }) - .catch(error => { - showMessage('Error creating dataset', 'error'); - }); - }); - - // Botón de eliminar dataset - document.getElementById('delete-dataset-btn').addEventListener('click', function () { - if (!currentDatasetId) { - showMessage('No dataset selected', 'error'); - return; - } - - const dataset = currentDatasets[currentDatasetId]; - if (confirm(`Are you sure you want to delete dataset "${dataset.name}"?`)) { - fetch(`/api/datasets/${currentDatasetId}`, { - method: 'DELETE' - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - showMessage(data.message, 'success'); - loadDatasets(); - } else { - showMessage(data.message, 'error'); - } - }) - .catch(error => { - showMessage('Error deleting dataset', 'error'); - }); - } - }); - - // Botón de activar dataset - document.getElementById('activate-dataset-btn').addEventListener('click', function () { - if (!currentDatasetId) return; - - fetch(`/api/datasets/${currentDatasetId}/activate`, { - method: 'POST' - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - showMessage(data.message, 'success'); - loadDatasets(); - } else { - showMessage(data.message, 'error'); - } - }) - .catch(error => { - showMessage('Error activating dataset', 'error'); - }); - }); - - // Botón de desactivar dataset - document.getElementById('deactivate-dataset-btn').addEventListener('click', function () { - if (!currentDatasetId) return; - - fetch(`/api/datasets/${currentDatasetId}/deactivate`, { - method: 'POST' - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - showMessage(data.message, 'success'); - loadDatasets(); - } else { - showMessage(data.message, 'error'); - } - }) - .catch(error => { - showMessage('Error deactivating dataset', 'error'); - }); - }); - - // Formulario de variables - document.getElementById('variable-form').addEventListener('submit', function (e) { - e.preventDefault(); - - if (!currentDatasetId) { - showMessage('No dataset selected. Please select a dataset first.', 'error'); - return; - } - - const area = document.getElementById('var-area').value; - const data = { - name: document.getElementById('var-name').value, - area: area, - db: area === 'db' ? parseInt(document.getElementById('var-db').value) : 1, - offset: parseInt(document.getElementById('var-offset').value), - type: document.getElementById('var-type').value, - streaming: false // Default to not streaming - }; - - // Añadir parámetro bit para áreas de bit - if (area === 'e' || area === 'a' || area === 'mb') { - data.bit = parseInt(document.getElementById('var-bit').value); - } - - fetch(`/api/datasets/${currentDatasetId}/variables`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(data) - }) - .then(response => response.json()) - .then(data => { - showMessage(data.message, data.success ? 'success' : 'error'); - if (data.success) { - document.getElementById('variable-form').reset(); - loadDatasets(); // Recargar para actualizar conteos - updateStatus(); - } - }); - }); -} - -// Eliminar variable del dataset actual -function removeVariable(name) { - if (!currentDatasetId) { - showMessage('No dataset selected', 'error'); - return; - } - - if (confirm(`Are you sure you want to remove the variable "${name}" from this dataset?`)) { - fetch(`/api/datasets/${currentDatasetId}/variables/${name}`, { method: 'DELETE' }) - .then(response => response.json()) - .then(data => { - showMessage(data.message, data.success ? 'success' : 'error'); - if (data.success) { - loadDatasets(); // Recargar para actualizar conteos - updateStatus(); - } - }); - } -} - -// Variables para edición de variables -let currentEditingVariable = null; - -// Editar variable -function editVariable(name) { - if (!currentDatasetId) { - showMessage('No dataset selected', 'error'); - return; - } - - currentEditingVariable = name; - - // Obtener datos de la variable del dataset actual - const dataset = currentDatasets[currentDatasetId]; - if (dataset && dataset.variables && dataset.variables[name]) { - const variable = dataset.variables[name]; - const streamingVars = dataset.streaming_variables || []; - - // Crear objeto de variable con la misma estructura que la API - const variableData = { - name: name, - area: variable.area, - db: variable.db, - offset: variable.offset, - type: variable.type, - bit: variable.bit, - streaming: streamingVars.includes(name) - }; - - populateEditForm(variableData); - document.getElementById('edit-modal').style.display = 'block'; - } else { - showMessage('Variable not found in current dataset', 'error'); - } -} - -// Rellenar formulario de edición -function populateEditForm(variable) { - document.getElementById('edit-var-name').value = variable.name; - document.getElementById('edit-var-area').value = variable.area; - document.getElementById('edit-var-offset').value = variable.offset; - document.getElementById('edit-var-type').value = variable.type; - - if (variable.db) { - document.getElementById('edit-var-db').value = variable.db; - } - - if (variable.bit !== undefined) { - document.getElementById('edit-var-bit').value = variable.bit; - } - - // Actualizar visibilidad de campos según el área - toggleEditFields(); -} - -// Cerrar modal de edición -function closeEditModal() { - document.getElementById('edit-modal').style.display = 'none'; - currentEditingVariable = null; -} - -// Inicializar listeners para edición de variables -function initVariableEditListeners() { - // Manejar envío del formulario de edición - document.getElementById('edit-variable-form').addEventListener('submit', function (e) { - e.preventDefault(); - - if (!currentEditingVariable || !currentDatasetId) { - showMessage('No variable or dataset selected for editing', 'error'); - return; - } - - const area = document.getElementById('edit-var-area').value; - const newName = document.getElementById('edit-var-name').value; - - // Primero eliminar la variable antigua - fetch(`/api/datasets/${currentDatasetId}/variables/${currentEditingVariable}`, { - method: 'DELETE' - }) - .then(response => response.json()) - .then(deleteResult => { - if (deleteResult.success) { - // Luego añadir la variable actualizada - const data = { - name: newName, - area: area, - db: area === 'db' ? parseInt(document.getElementById('edit-var-db').value) : 1, - offset: parseInt(document.getElementById('edit-var-offset').value), - type: document.getElementById('edit-var-type').value, - streaming: false // Se restaurará abajo si estaba habilitado - }; - - // Añadir parámetro bit para áreas de bit - if (area === 'e' || area === 'a' || area === 'mb') { - data.bit = parseInt(document.getElementById('edit-var-bit').value); - } - - return fetch(`/api/datasets/${currentDatasetId}/variables`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(data) - }); - } else { - throw new Error(deleteResult.message); - } - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - showMessage('Variable updated successfully', 'success'); - closeEditModal(); - loadDatasets(); - updateStatus(); - } else { - showMessage(data.message, 'error'); - } - }) - .catch(error => { - showMessage(`Error updating variable: ${error}`, 'error'); - }); - }); - - // Cerrar modal al hacer clic fuera de él - window.onclick = function (event) { - const editModal = document.getElementById('edit-modal'); - const datasetModal = document.getElementById('dataset-modal'); - if (event.target === editModal) { - closeEditModal(); - } - if (event.target === datasetModal) { - datasetModal.style.display = 'none'; - } - } -} \ No newline at end of file diff --git a/static/js/events.js b/static/js/events.js deleted file mode 100644 index 0f1e078..0000000 --- a/static/js/events.js +++ /dev/null @@ -1,185 +0,0 @@ -/** - * Gestión de eventos de la aplicación y log de eventos - */ - -// Refrescar log de eventos -function refreshEventLog() { - const limitElement = document.getElementById('log-limit'); - const limit = limitElement ? limitElement.value : 100; - - fetch(`/api/events?limit=${limit}`) - .then(response => response.json()) - .then(data => { - if (data.success) { - const logContainer = document.getElementById('events-container'); - const logStats = document.getElementById('events-count'); - - // Verificar que los elementos existan - if (!logContainer) { - console.warn('Events container not found'); - return; - } - - // Limpiar entradas existentes - logContainer.innerHTML = ''; - - // Actualizar estadísticas - if (logStats) { - logStats.textContent = `${data.showing} of ${data.total_events}`; - } - - // Añadir eventos (orden inverso para mostrar primero los más nuevos) - const events = data.events.reverse(); - - if (events.length === 0) { - logContainer.innerHTML = ` -
-
- 📋 System - ${new Date().toLocaleString('es-ES')} -
-
No events found
-
- `; - } else { - events.forEach(event => { - logContainer.appendChild(createLogEntry(event)); - }); - } - - // Auto-scroll al inicio para mostrar eventos más nuevos - logContainer.scrollTop = 0; - } else { - console.error('Error loading events:', data.error); - showMessage('Error loading events log', 'error'); - } - }) - .catch(error => { - console.error('Error fetching events:', error); - showMessage('Error fetching events log', 'error'); - }); -} - -// Crear entrada de log -function createLogEntry(event) { - const logEntry = document.createElement('div'); - logEntry.className = `log-entry log-${event.level}`; - - const hasDetails = event.details && Object.keys(event.details).length > 0; - - logEntry.innerHTML = ` -
- ${getEventIcon(event.event_type)} ${event.event_type.replace(/_/g, ' ').toUpperCase()} - ${formatTimestamp(event.timestamp)} -
-
${event.message}
- ${hasDetails ? `
${JSON.stringify(event.details, null, 2)}
` : ''} - `; - - return logEntry; -} - -// Limpiar vista de log -function clearLogView() { - const logContainer = document.getElementById('events-log'); - logContainer.innerHTML = ` -
-
- 🧹 System - ${new Date().toLocaleString('es-ES')} -
-
Log view cleared. Click refresh to reload events.
-
- `; - - const logStats = document.getElementById('log-stats'); - logStats.textContent = 'Log view cleared'; -} - -// Inicializar listeners para eventos -function initEventListeners() { - // Botones de control de log para el tab de events - const refreshBtn = document.getElementById('refresh-events-btn'); - const clearBtn = document.getElementById('clear-events-btn'); - - if (refreshBtn) { - refreshBtn.addEventListener('click', loadEvents); - } - - if (clearBtn) { - clearBtn.addEventListener('click', clearEventsView); - } -} - -// Función para cargar eventos en el tab de events -window.loadEvents = function () { - fetch('/api/events?limit=50') - .then(response => response.json()) - .then(data => { - if (data.success) { - const eventsContainer = document.getElementById('events-container'); - const eventsCount = document.getElementById('events-count'); - - // Verificar que los elementos existan - if (!eventsContainer) { - console.warn('Events container not found in loadEvents'); - return; - } - - // Limpiar contenedor - eventsContainer.innerHTML = ''; - - // Actualizar contador - if (eventsCount) { - eventsCount.textContent = data.showing || 0; - } - - // Añadir eventos (orden inverso para mostrar primero los más nuevos) - const events = data.events.reverse(); - - if (events.length === 0) { - eventsContainer.innerHTML = ` -
-
- 📋 System - ${new Date().toLocaleString('es-ES')} -
-
No events found
-
- `; - } else { - events.forEach(event => { - eventsContainer.appendChild(createLogEntry(event)); - }); - } - - // Auto-scroll al inicio para mostrar eventos más nuevos - eventsContainer.scrollTop = 0; - } else { - console.error('Error loading events:', data.error); - showMessage('Error loading events log', 'error'); - } - }) - .catch(error => { - console.error('Error fetching events:', error); - showMessage('Error fetching events log', 'error'); - }); -} - -// Función para limpiar vista de eventos -function clearEventsView() { - const eventsContainer = document.getElementById('events-container'); - const eventsCount = document.getElementById('events-count'); - - eventsContainer.innerHTML = ` -
-
- 🧹 System - ${new Date().toLocaleString('es-ES')} -
-
Events view cleared. Click refresh to reload events.
-
- `; - - eventsCount.textContent = '0'; -} \ No newline at end of file diff --git a/static/js/main.js b/static/js/main.js deleted file mode 100644 index 916ecb9..0000000 --- a/static/js/main.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Archivo principal que inicializa todos los componentes - */ - -// Inicializar la aplicación al cargar el documento -document.addEventListener('DOMContentLoaded', function () { - // Inicializar tema - loadTheme(); - - // Iniciar streaming de estado automáticamente - startStatusStreaming(); - - // Cargar datos iniciales - loadDatasets(); - updateStatus(); - loadCsvConfig(); - refreshEventLog(); - - // Inicializar listeners de eventos - initPlcListeners(); - initDatasetListeners(); - initVariableEditListeners(); - initStreamingListeners(); - initCsvListeners(); - initEventListeners(); - - // 🔑 NUEVO: Inicializar plotManager si existe - if (typeof PlotManager !== 'undefined' && !window.plotManager) { - window.plotManager = new PlotManager(); - } - - // Configurar actualizaciones periódicas como respaldo - setInterval(updateStatus, 30000); // Cada 30 segundos como respaldo - setInterval(refreshEventLog, 10000); // Cada 10 segundos - - // Inicializar visibilidad de campos en formularios - toggleFields(); -}); - -// Limpiar conexiones SSE cuando se descarga la página -window.addEventListener('beforeunload', function () { - if (variableEventSource) { - variableEventSource.close(); - } - if (statusEventSource) { - statusEventSource.close(); - } -}); \ No newline at end of file diff --git a/static/js/plc.js b/static/js/plc.js deleted file mode 100644 index 9f3fd1d..0000000 --- a/static/js/plc.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Gestión de la conexión con el PLC y configuración relacionada - */ - -// Inicializar listeners de eventos para PLC -function initPlcListeners() { - // Configuración del PLC - document.getElementById('plc-config-form').addEventListener('submit', function (e) { - e.preventDefault(); - const data = { - ip: document.getElementById('plc-ip').value, - rack: parseInt(document.getElementById('plc-rack').value), - slot: parseInt(document.getElementById('plc-slot').value) - }; - - fetch('/api/plc/config', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(data) - }) - .then(response => response.json()) - .then(data => { - showMessage(data.message, data.success ? 'success' : 'error'); - }); - }); - - // Configuración UDP - document.getElementById('udp-config-form').addEventListener('submit', function (e) { - e.preventDefault(); - const data = { - host: document.getElementById('udp-host').value, - port: parseInt(document.getElementById('udp-port').value) - }; - - fetch('/api/udp/config', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(data) - }) - .then(response => response.json()) - .then(data => { - showMessage(data.message, data.success ? 'success' : 'error'); - }); - }); - - // Botón de conexión PLC - document.getElementById('connect-btn').addEventListener('click', function () { - fetch('/api/plc/connect', { method: 'POST' }) - .then(response => response.json()) - .then(data => { - showMessage(data.message, data.success ? 'success' : 'error'); - updateStatus(); - }); - }); - - // Botón de desconexión PLC - document.getElementById('disconnect-btn').addEventListener('click', function () { - fetch('/api/plc/disconnect', { method: 'POST' }) - .then(response => response.json()) - .then(data => { - showMessage(data.message, data.success ? 'success' : 'error'); - updateStatus(); - }); - }); - - // Botón de actualización de intervalo - document.getElementById('update-sampling-btn').addEventListener('click', function () { - const interval = parseFloat(document.getElementById('sampling-interval').value); - fetch('/api/sampling', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ interval: interval }) - }) - .then(response => response.json()) - .then(data => { - showMessage(data.message, data.success ? 'success' : 'error'); - }); - }); -} \ No newline at end of file diff --git a/static/js/plotting.js b/static/js/plotting.js deleted file mode 100644 index dc2cedb..0000000 --- a/static/js/plotting.js +++ /dev/null @@ -1,2080 +0,0 @@ -/** - * 📈 Real-Time Plotting System - * Maneja sesiones de plotting con Chart.js y comunicación con el backend - */ - -// Helper function para logging condicional (solo errores graves) -function plotDebugLog(...args) { - // Solo mantener para compatibilidad, no hacer nada -} - -// Test function removida - solo mantener para compatibilidad -window.testPlotSystem = async () => { - // Función removida para limpiar debug -}; - -class PlotManager { - constructor() { - this.sessions = new Map(); // session_id -> Chart instance - this.updateInterval = null; - this.statusUpdateInterval = null; // 🔑 NUEVO: Para updates de status - this.isInitialized = false; - // El render lo maneja el plugin a ~30 FPS; no exponemos refresh rate en UI - this.refreshRates = new Map(); // aún usado internamente para onRefresh - - // Colores para las variables - this.colors = [ - '#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF', - '#FF9F40', '#8A2BE2', '#FF1493', '#00CED1', '#32CD32', - '#FFB347', '#DA70D6', '#40E0D0', '#EE82EE', '#90EE90' - ]; - - // Variables seleccionadas para el formulario - this.selectedVariables = new Map(); // variable_name -> { color, dataset } - this.availableDatasets = []; - this.currentEditingSession = null; - - this.init(); - } - - // Asegura el toggle correcto de la sección de Trigger dentro del formulario (usado en edición) - togglePlotFormTriggerConfig() { - try { - const triggerEnabled = document.getElementById('plot-form-trigger-enabled'); - const triggerConfig = document.getElementById('plot-form-trigger-config'); - if (triggerConfig && triggerEnabled) { - triggerConfig.style.display = triggerEnabled.checked ? 'block' : 'none'; - } - } catch (e) { - console.warn('togglePlotFormTriggerConfig failed', e); - } - } - - init() { - // Cargar sesiones existentes al inicializar - this.loadExistingSessions(); - - // Inicializar formulario colapsable - this.initCollapsibleForm(); - - // Inicializar modal de selección de variables - this.initVariableModal(); - - // Iniciar actualización automática - this.startAutoUpdate(); - - this.isInitialized = true; - } - - async loadExistingSessions() { - try { - const response = await fetch('/api/plots'); - const data = await response.json(); - - if (data.sessions) { - for (const session of data.sessions) { - // 🔑 NUEVO: Obtener configuración completa para cada sesión - try { - const configResponse = await fetch(`/api/plots/${session.session_id}/config`); - const configData = await configResponse.json(); - - let sessionInfo = session; - - // Si tenemos la configuración completa, usar esa información - if (configData.success && configData.config) { - sessionInfo = { - ...session, - is_active: configData.config.is_active, - is_paused: configData.config.is_paused, - variables_count: configData.config.variables ? configData.config.variables.length : session.variables_count, - trigger_enabled: configData.config.trigger_enabled, - trigger_variable: configData.config.trigger_variable, - trigger_on_true: configData.config.trigger_on_true - }; - } - - await this.createPlotSessionTab(session.session_id, sessionInfo); - } catch (configError) { - console.error(`Error loading config for session ${session.session_id}:`, configError); - // Usar información básica si falla la carga de configuración - await this.createPlotSessionTab(session.session_id, session); - } - } - } - } catch (error) { - console.error('Error loading existing plot sessions:', error); - } - } - - startAutoUpdate() { - // Solo actualizar status cada 5 segundos para mantener sincronización - // Los datos se actualizan automáticamente via chartjs-plugin-streaming - this.statusUpdateInterval = setInterval(() => { - this.updateAllSessionsStatus(); - }, 5000); - } - - stopAutoUpdate() { - if (this.statusUpdateInterval) { - clearInterval(this.statusUpdateInterval); - this.statusUpdateInterval = null; - } - } - - // Actualizar status de todas las sesiones (solo status, no datos) - async updateAllSessionsStatus() { - const activeSessions = Array.from(this.sessions.keys()); - - for (const sessionId of activeSessions) { - await this.updateSessionStatus(sessionId); - } - } - - createPlotSession(sessionId, config) { - // Crear contenedor para el plot - const container = document.createElement('div'); - container.className = 'plot-session'; - container.id = `plot-${sessionId}`; - - container.innerHTML = ` -
-

📈 ${config.name || `Plot ${sessionId}`}

-
- - - - - - -
-
-
- - Variables: ${config.variables_count || 0} | - Data Points: 0 | - ${config.trigger_enabled ? `Trigger: ${config.trigger_variable} (${config.trigger_on_true ? 'True' : 'False'})` : 'No Trigger'} - -
-
- -
- `; - - document.getElementById('plot-sessions-container').appendChild(container); - - // Crear configuración Chart.js con streaming - this.createStreamingChart(sessionId, config); - } - - /** - * Crea un chart de streaming usando chartjs-plugin-streaming o modo fallback - */ - createStreamingChart(sessionId, config) { - const ctx = document.getElementById(`chart-${sessionId}`).getContext('2d'); - - let chartConfig = this.createStreamingChartConfig(sessionId, config); - - // Pre-poblar datasets en config antes de crear el Chart para evitar metas undefined - const enabledVariables = this.getEnabledVariables(config.variables); - if (enabledVariables.length > 0) { - const datasets = []; - enabledVariables.forEach((variableInfo) => { - const color = variableInfo.color || this.getColor(variableInfo.name, datasets.length); - datasets.push({ - label: variableInfo.name, - data: [], - borderColor: color, - backgroundColor: color + '20', - borderWidth: 2, - fill: false, - // Unir huecos por defecto; los cortes reales los forzamos con NaN al reanudar - spanGaps: true, - pointRadius: 1, - pointHoverRadius: 4, - cubicInterpolationMode: 'monotone', - // stepped: true, - tension: 0.4 - }); - }); - chartConfig.data.datasets = datasets; - } - - // Crear chart intentando modo realtime; si falla, usar fallback time - let chart; - let isRealTimeMode = true; - try { - chart = new Chart(ctx, chartConfig); - console.log(`✅ Plot ${sessionId}: Real-time Streaming enabled`); - } catch (e) { - console.warn(`⚠️ Plot ${sessionId}: Real-time scale not available. Falling back to time scale.`, e); - chartConfig = this.createFallbackChartConfig(sessionId, config); - chart = new Chart(ctx, chartConfig); - isRealTimeMode = false; - } - - // Guardar en sessions - this.sessions.set(sessionId, { - chart: chart, - config: config, - lastDataFetch: 0, - datasetIndex: new Map(), // Mapeo de variable -> índice de dataset - isRealTimeMode: isRealTimeMode, - lastPushedXByDataset: new Map(), // índice de dataset -> último timestamp x añadido - ingestPaused: false, // si true, no se agregan puntos nuevos (marca hueco) - insertNaNOnNextIngest: false, - isPaused: false - }); - - // Fijar refresh de datos base (no de render). El render es 30 FPS en el plugin. - if (!this.refreshRates.has(sessionId)) { - this.refreshRates.set(sessionId, 1000); - } - - // Inicializar índice de datasets (sin mutar chart aún) - if (Array.isArray(config.variables)) { - config.variables.forEach((variable, index) => { - this.sessions.get(sessionId).datasetIndex.set(variable, index); - }); - } - - // Si no es modo realtime, iniciar intervalo manual - if (!isRealTimeMode) { - const refreshRate = this.refreshRates.get(sessionId) || 1000; - console.log(`🔄 Plot ${sessionId}: Starting manual refresh with ${refreshRate}ms interval`); - this.startManualRefresh(sessionId); - } - } - - /** - * Configuración para modo streaming (con chartjs-plugin-streaming) - */ - createStreamingChartConfig(sessionId, config) { - return { - type: 'line', - data: { - datasets: [] - }, - options: { - responsive: true, - maintainAspectRatio: false, - animation: false, - layout: { - padding: { bottom: 16 } - }, - - scales: { - x: { - type: 'realtime', - realtime: { - duration: (config.time_window || 60) * 1000, - refresh: this.refreshRates.get(sessionId) || 1000, // Actualizar según configuración dinámica - delay: ((this.refreshRates.get(sessionId) || 1000) * 2), // Margen temporal para evitar saltos - frameRate: 30, - pause: false, // No pausar inicialmente; el estado real se aplicará vía updateSessionStatus - onRefresh: (chart) => { - this.onStreamingRefresh(sessionId, chart); - } - }, - // Asegurar etiquetas de tiempo visibles y formateadas - ticks: { - display: true, - autoSkip: true, - maxRotation: 0 - }, - // Formatos de visualización para diferentes unidades de tiempo - time: { - displayFormats: { - millisecond: 'HH:mm:ss.SSS', - second: 'HH:mm:ss', - minute: 'HH:mm', - hour: 'HH:mm' - } - }, - title: { - display: true, - text: 'Tiempo' - } - }, - y: { - title: { - display: true, - text: 'Valor' - }, - min: config.y_min, - max: config.y_max - } - }, - - plugins: { - legend: { - display: true, - position: 'top' - }, - tooltip: { - mode: 'index', - intersect: false - }, - // Integración con chartjs-plugin-zoom (pan/zoom en eje X) - zoom: { - pan: { - enabled: true, - mode: 'x' - }, - zoom: { - pinch: { enabled: true }, - wheel: { enabled: true }, - mode: 'x' - }, - limits: { - x: { - // Permitir variar delay/duration en un rango razonable - minDelay: 0, - maxDelay: 4000, - minDuration: 1000, - maxDuration: (config.time_window || 60) * 1000 - } - } - } - }, - - interaction: { - mode: 'nearest', - axis: 'x', - intersect: false - }, - - elements: { - point: { - radius: 0, - hoverRadius: 3 - }, - line: { - tension: 0.1, - borderWidth: 2 - } - } - } - }; - } - - /** - * Configuración fallback (sin chartjs-plugin-streaming) - */ - createFallbackChartConfig(sessionId, config) { - return { - type: 'line', - data: { - datasets: [] - }, - options: { - responsive: true, - maintainAspectRatio: false, - animation: false, - layout: { - padding: { bottom: 16 } - }, - - scales: { - x: { - type: 'time', - time: { - unit: 'second', - displayFormats: { - second: 'HH:mm:ss' - } - }, - ticks: { - display: true, - autoSkip: true, - maxRotation: 0 - }, - title: { - display: true, - text: 'Tiempo' - } - }, - y: { - title: { - display: true, - text: 'Valor' - }, - min: config.y_min, - max: config.y_max - } - }, - - plugins: { - legend: { - display: true, - position: 'top' - }, - tooltip: { - mode: 'index', - intersect: false - } - }, - - interaction: { - mode: 'nearest', - axis: 'x', - intersect: false - }, - - elements: { - point: { - radius: 0, - hoverRadius: 3 - }, - line: { - tension: 0.1, - borderWidth: 2 - } - } - } - }; - } - - /** - * Inicia actualización manual para modo fallback - */ - startManualRefresh(sessionId) { - const sessionData = this.sessions.get(sessionId); - if (!sessionData) return; - - // Limpiar intervalo existente si hay uno - if (sessionData.manualInterval) { - clearInterval(sessionData.manualInterval); - } - - // Crear intervalo de actualización manual con refresh rate dinámico - const refreshRate = this.refreshRates.get(sessionId) || 1000; - console.log(`⏲️ Plot ${sessionId}: Manual interval created with ${refreshRate}ms`); - sessionData.manualInterval = setInterval(() => { - this.onStreamingRefresh(sessionId, sessionData.chart); - }, refreshRate); - } - - /** - * Inicializa los datasets del chart con las variables configuradas - */ - initializeChartDatasets(sessionId, config) { - const sessionData = this.sessions.get(sessionId); - if (!sessionData || !config.variables) { - return; - } - - const chart = sessionData.chart; - const datasets = []; - - const enabledVariables = this.getEnabledVariables(config.variables); - enabledVariables.forEach((variableInfo) => { - const color = variableInfo.color || this.getColor(variableInfo.name, datasets.length); - const dataset = { - label: variableInfo.name, - data: [], - borderColor: color, - backgroundColor: color + '20', - borderWidth: 2, - fill: false, - spanGaps: true, - pointRadius: 0, - pointHoverRadius: 3, - tension: 0.1 - }; - - datasets.push(dataset); - sessionData.datasetIndex.set(variableInfo.name, datasets.length - 1); - }); - - chart.data.datasets = datasets; - chart.update('quiet'); - } - - /** - * Función llamada automáticamente por chartjs-plugin-streaming - * para obtener nuevos datos del cache del PLC - */ - async onStreamingRefresh(sessionId, chart) { - try { - const sessionData = this.sessions.get(sessionId); - if (!sessionData) { - return; - } - - // Evitar llamadas muy frecuentes basado en el refresh rate dinámico - const now = Date.now(); - const refreshRate = this.refreshRates.get(sessionId) || 1000; - const minInterval = Math.max(refreshRate * 0.5, 50); // menos restrictivo para alta frecuencia - - if (now - sessionData.lastDataFetch < minInterval) { - const timeSinceLastUpdate = now - sessionData.lastDataFetch; - console.log(`⏭️ Plot ${sessionId}: Skipping update (${timeSinceLastUpdate}ms < ${minInterval}ms threshold)`); - return; - } - - const timeSinceLastUpdate = now - sessionData.lastDataFetch; - sessionData.lastDataFetch = now; - - // Obtener datos del backend (que usa solo cache) - const response = await fetch(`/api/plots/${sessionId}/data`); - if (!response.ok) return; - - const plotData = await response.json(); - - // 🔑 MODIFICADO: Llamar siempre a addNewDataToStreaming para asegurar el panning continuo. - // La función se encargará de manejar si hay datos nuevos o no. - this.addNewDataToStreaming(sessionId, plotData, now); - this.updatePointsCounter(sessionId, plotData); - - } catch (error) { - console.error(`📈 Error in streaming refresh for ${sessionId}:`, error); - } - } - - /** - * Agrega nuevos datos al chart de streaming (tanto realtime como fallback) - */ - addNewDataToStreaming(sessionId, plotData, timestamp) { - const sessionData = this.sessions.get(sessionId); - if (!sessionData || !sessionData.chart) { - return; - } - - const chart = sessionData.chart; - const isRealTimeMode = sessionData.isRealTimeMode; - - // Si está en pausa, no ingerir datos ni empujar puntos - try { - if (isRealTimeMode) { - const xScale = chart.scales && chart.scales.x; - if (xScale && xScale.realtime && xScale.realtime.pause) { - return; - } - } - if (sessionData.ingestPaused) { - return; - } - } catch (_) { /* noop */ } - - // En modo realtime, dejamos el panning continuo al plugin mediante 'delay'. - // Empujar todos los puntos nuevos disponibles para minimizar huecos. - let pointsAdded = 0; - chart.data.datasets.forEach((chartDataset, datasetIndex) => { - const lastPushedX = sessionData.lastPushedXByDataset.get(datasetIndex) || 0; - - const backendDataset = plotData && plotData.datasets ? plotData.datasets[datasetIndex] : undefined; - if (!backendDataset || !Array.isArray(backendDataset.data) || backendDataset.data.length === 0) { - return; // No hay datos nuevos para este dataset - } - - // Filtrar y normalizar puntos con y válido y x en ms - const newPoints = []; - for (let i = 0; i < backendDataset.data.length; i++) { - const p = backendDataset.data[i]; - const yNum = (typeof p.y === 'number') ? p.y : Number(p.y); - if (!isFinite(yNum)) continue; - let xNum = (typeof p.x === 'number') ? p.x : Number(p.x); - if (!isFinite(xNum)) continue; - if (xNum < 1e12) xNum = xNum * 1000; // segundos -> ms - if (xNum > lastPushedX) newPoints.push({ x: xNum, y: yNum }); - } - - if (newPoints.length === 0) { - return; - } - - // Ordenar por x y garantizar monotonicidad con el último x - newPoints.sort((a, b) => a.x - b.x); - - // Insertar NaN solo una vez al reanudar, antes del primer punto nuevo - if (sessionData.insertNaNOnNextIngest) { - const firstX = Math.max(newPoints[0].x, lastPushedX + 1); - let gapX = firstX - 1; - if (gapX <= lastPushedX) { - gapX = firstX - 0.001; // asegurar x estrictamente mayor que lastPushedX - } - chartDataset.data.push({ x: gapX, y: NaN }); - } - - // Empujar todos los puntos nuevos ajustando x para ser estrictamente creciente - let lastX = lastPushedX; - for (const p of newPoints) { - const finalX = Math.max(p.x, lastX + 1); - chartDataset.data.push({ x: finalX, y: p.y }); - lastX = finalX; - pointsAdded++; - } - sessionData.lastPushedXByDataset.set(datasetIndex, lastX); - }); - - // En modo fallback (sin realtime), limpieza y update manual - if (!isRealTimeMode) { - this.cleanupOldDataFallback(sessionId); - chart.update('quiet'); - } else if (pointsAdded > 0) { - // Modelo pull asíncrono: tras añadir datos en onRefresh, actualizar de forma 'quiet' - // Referencia: guía oficial (Pull Model - Asynchronous) - chart.update('quiet'); - } - - // Si se insertaron puntos en este ciclo, limpiar la marca para no seguir insertando NaN - if (pointsAdded > 0 && sessionData.insertNaNOnNextIngest) { - sessionData.insertNaNOnNextIngest = false; - } - } - - /** - * Limpia datos antiguos en modo fallback - */ - cleanupOldDataFallback(sessionId) { - const sessionData = this.sessions.get(sessionId); - if (!sessionData) return; - - const chart = sessionData.chart; - const timeWindow = (sessionData.config.time_window || 60) * 1000; - const now = Date.now(); - const cutoffTime = now - timeWindow; - - chart.data.datasets.forEach((dataset, index) => { - if (dataset.data && dataset.data.length > 0) { - const originalLength = dataset.data.length; - dataset.data = dataset.data.filter(point => point.x > cutoffTime); - } - }); - } - - /** - * Actualiza el contador de puntos en la UI - */ - updatePointsCounter(sessionId, plotData) { - const pointsElement = document.getElementById(`points-${sessionId}`); - if (pointsElement) { - const totalPoints = plotData.data_points_count || 0; - pointsElement.textContent = totalPoints; - } - } - - updateChart(sessionId, plotData) { - // Esta función es llamada por el sistema legacy pero no se usa en streaming - // Los datos se actualizan automáticamente via onStreamingRefresh - - // Solo actualizar contador de puntos - this.updatePointsCounter(sessionId, plotData); - } - - // Función para actualizar datos de una sesión específica (llamada desde tabs.js) - async updateSessionData(sessionId) { - if (!this.sessions.has(sessionId)) { - return; - } - - try { - // Obtener datos frescos del servidor - const response = await fetch(`/api/plots/${sessionId}/data`); - if (!response.ok) { - console.error(`📈 Plot ${sessionId}: Failed to fetch data`); - return; - } - - const plotData = await response.json(); - - // Actualizar contador de puntos - this.updatePointsCounter(sessionId, plotData); - - } catch (error) { - console.error(`📈 Plot ${sessionId}: Error updating session data:`, error); - } - } - - async controlPlot(sessionId, action) { - try { - // Controlar streaming localmente - if (action === 'pause') { - this.pauseStreaming(sessionId); - } else if (action === 'start') { - this.resumeStreaming(sessionId); - } else if (action === 'clear') { - this.clearStreamingData(sessionId); - } - - const response = await fetch(`/api/plots/${sessionId}/control`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ action: action }) - }); - - const result = await response.json(); - - if (result.success) { - // Actualizar status después de la acción - await this.updateSessionStatus(sessionId); - - // Controlar streaming según la acción - if (action === 'stop') { - this.pauseStreaming(sessionId); - } else if (action === 'start') { - this.resumeStreaming(sessionId); - } - - showNotification(result.message, 'success'); - } else { - showNotification(result.error, 'error'); - } - } catch (error) { - console.error(`Error controlling plot ${sessionId}:`, error); - showNotification('Error controlling plot session', 'error'); - } - } - - /** - * Pausa el streaming de un chart (realtime o fallback) - */ - pauseStreaming(sessionId) { - const sessionData = this.sessions.get(sessionId); - if (!sessionData) return; - - if (sessionData.isRealTimeMode) { - // Modo realtime - pausar escala - const chart = sessionData.chart; - const xScale = chart.scales.x; - if (xScale && xScale.realtime) { - xScale.realtime.pause = true; - } - if (chart.options && chart.options.scales && chart.options.scales.x && chart.options.scales.x.realtime) { - chart.options.scales.x.realtime.pause = true; - } - chart.update('quiet'); - - // Marcar pausa de ingesta para crear hueco (NaN) en reanudación - sessionData.ingestPaused = true; - sessionData.isPaused = true; - } else { - // Modo fallback - pausar intervalo manual - if (sessionData.manualInterval) { - clearInterval(sessionData.manualInterval); - sessionData.manualInterval = null; - } - sessionData.ingestPaused = true; - sessionData.isPaused = true; - } - } - - /** - * Reanuda el streaming de un chart (realtime o fallback) - */ - resumeStreaming(sessionId) { - const sessionData = this.sessions.get(sessionId); - if (!sessionData) return; - - if (sessionData.isRealTimeMode) { - // Modo realtime - reanudar escala - const chart = sessionData.chart; - const xScale = chart.scales.x; - if (xScale && xScale.realtime) { - xScale.realtime.pause = false; - } - if (chart.options && chart.options.scales && chart.options.scales.x && chart.options.scales.x.realtime) { - chart.options.scales.x.realtime.pause = false; - } - chart.update('quiet'); - } else { - // Modo fallback - reanudar intervalo manual - if (!sessionData.manualInterval) { - this.startManualRefresh(sessionId); - } - } - // Marcar para insertar un único NaN en la próxima ingesta - sessionData.insertNaNOnNextIngest = true; - sessionData.ingestPaused = false; - sessionData.isPaused = false; - } - - /** - * Limpia todos los datos del chart - */ - clearStreamingData(sessionId) { - const sessionData = this.sessions.get(sessionId); - if (sessionData && sessionData.chart) { - sessionData.chart.data.datasets.forEach(dataset => { - if (dataset.data) { - dataset.data.length = 0; - } - }); - sessionData.chart.update('quiet'); - } - } - - // 🔑 NUEVO: Método para actualizar solo el status del plot - async updateSessionStatus(sessionId) { - try { - const response = await fetch(`/api/plots/${sessionId}/config`); - const data = await response.json(); - - if (data.success && data.config) { - this.updatePlotStats(sessionId, data.config); - - // Actualizar estado de streaming basado en el status del backend - const sessionData = this.sessions.get(sessionId); - if (sessionData) { - // Actualizar configuración local - sessionData.config = data.config; - - // Controlar pausa del streaming basado en el estado del plot - const shouldPause = !data.config.is_active || data.config.is_paused; - if (shouldPause) { - this.pauseStreaming(sessionId); - } else { - this.resumeStreaming(sessionId); - } - } - } - } catch (error) { - console.error(`Error updating session status ${sessionId}:`, error); - } - } - - async removePlot(sessionId) { - try { - const response = await fetch(`/api/plots/${sessionId}`, { - method: 'DELETE' - }); - - const result = await response.json(); - - if (result.success) { - this.removeChart(sessionId); - showNotification('Plot session removed', 'success'); - } else { - showNotification(result.error, 'error'); - } - } catch (error) { - console.error(`Error removing plot ${sessionId}:`, error); - showNotification('Error removing plot session', 'error'); - } - } - - removeChart(sessionId) { - // Destruir Chart.js y limpiar intervalos - const sessionData = this.sessions.get(sessionId); - if (sessionData) { - // Limpiar intervalo manual si existe - if (sessionData.manualInterval) { - clearInterval(sessionData.manualInterval); - } - - // Destruir chart - if (sessionData.chart) { - sessionData.chart.destroy(); - } - - this.sessions.delete(sessionId); - - // Limpiar refresh rate - this.refreshRates.delete(sessionId); - } - - // Remover contenedor - const container = document.getElementById(`plot-${sessionId}`); - if (container) { - container.remove(); - } - } - - // Funciones obsoletas eliminadas - reemplazadas por nuevas implementaciones - - async createPlotSessionTab(sessionId, sessionInfo) { - // Crear tab dinámico para el plot - if (typeof tabManager !== 'undefined') { - tabManager.createPlotTab(sessionId, sessionInfo.name || `Plot ${sessionId}`); - } - - // Obtener configuración completa del plot para el streaming - let plotConfig = sessionInfo; - try { - const configResponse = await fetch(`/api/plots/${sessionId}/config`); - const configData = await configResponse.json(); - if (configData.success && configData.config) { - plotConfig = configData.config; - } - } catch (error) { - console.warn(`Could not load plot config for ${sessionId}, using basic info`); - } - - // Crear el Chart.js con streaming - this.createStreamingChart(sessionId, plotConfig); - - // Actualizar estadísticas del plot - this.updatePlotStats(sessionId, sessionInfo); - - // Sincronizar controles del sub-tab (refresh rate y time window) - try { - const refreshInput = document.getElementById(`refresh-rate-tab-${sessionId}`); - if (refreshInput) { - refreshInput.value = this.refreshRates.get(sessionId) || 1000; - } - - const timeWindowInput = document.getElementById(`time-window-tab-${sessionId}`); - if (timeWindowInput) { - const sessionData = this.sessions.get(sessionId); - const currentWindow = (sessionData && sessionData.config && sessionData.config.time_window) ? sessionData.config.time_window : (plotConfig.time_window || 60); - timeWindowInput.value = currentWindow; - } - } catch (e) { - console.warn('Could not sync plot controls for session', sessionId, e); - } - } - - updatePlotStats(sessionId, sessionInfo) { - const statsElement = document.getElementById(`plot-stats-${sessionId}`); - if (statsElement) { - const status = sessionInfo.is_active ? - (sessionInfo.is_paused ? 'Paused' : 'Active') : - 'Stopped'; - - statsElement.innerHTML = ` - Variables: ${sessionInfo.variables_count || 0} | - Data Points: 0 | - Status: ${status} | - ${sessionInfo.trigger_enabled ? `Trigger: ${sessionInfo.trigger_variable} (${sessionInfo.trigger_on_true ? 'True' : 'False'})` : 'No Trigger'} - `; - } - } - - async createNewPlot(config) { - try { - const response = await fetch('/api/plots', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(config) - }); - - const result = await response.json(); - - if (result.success) { - // 🔑 NUEVO: Obtener configuración real del backend en lugar de hardcodear - const configResponse = await fetch(`/api/plots/${result.session_id}/config`); - const configData = await configResponse.json(); - - let sessionConfig = { - name: config.name, - variables_count: config.variables.length, - trigger_enabled: config.trigger_enabled, - trigger_variable: config.trigger_variable, - trigger_on_true: config.trigger_on_true, - is_active: false, // Por defecto stopped hasta que se obtenga del backend - is_paused: false - }; - - // Si tenemos la configuración real del backend, usarla - if (configData.success && configData.config) { - sessionConfig = { - ...sessionConfig, - is_active: configData.config.is_active, - is_paused: configData.config.is_paused - }; - } - - await this.createPlotSessionTab(result.session_id, sessionConfig); - - // Cambiar al sub-tab del nuevo plot - if (typeof tabManager !== 'undefined') { - tabManager.switchSubTab(`plot-${result.session_id}`); - } - - // 🔑 NUEVO: Los plots se auto-inician en el backend, no necesitamos start manual aquí - - showNotification(result.message, 'success'); - return result.session_id; - } else { - showNotification(result.error, 'error'); - return null; - } - } catch (error) { - console.error('Error creating new plot:', error); - showNotification('Error creating plot session', 'error'); - return null; - } - } - - initCollapsibleForm() { - const toggleBtn = document.getElementById('toggle-plot-form-btn'); - const formContainer = document.getElementById('plot-form-container'); - const closeBtn = document.getElementById('close-plot-form-btn'); - const cancelBtn = document.getElementById('cancel-plot-form'); - const plotForm = document.getElementById('plot-form'); - - if (toggleBtn) { - toggleBtn.addEventListener('click', () => { - this.showPlotForm(); - }); - } - - if (closeBtn) { - closeBtn.addEventListener('click', () => { - this.hidePlotForm(); - }); - } - - if (cancelBtn) { - cancelBtn.addEventListener('click', () => { - this.hidePlotForm(); - }); - } - - if (plotForm) { - plotForm.addEventListener('submit', (e) => { - e.preventDefault(); - this.handleFormSubmit(); - }); - } - - // Botón para abrir modal de variables - const selectVariablesBtn = document.getElementById('select-variables-btn'); - if (selectVariablesBtn) { - selectVariablesBtn.addEventListener('click', () => { - this.showVariableModal(); - }); - } - } - - initVariableModal() { - const modal = document.getElementById('variable-selection-modal'); - const closeBtn = document.getElementById('close-variable-modal'); - const cancelBtn = document.getElementById('cancel-variable-selection'); - const confirmBtn = document.getElementById('confirm-variable-selection'); - const selectAllBtn = document.getElementById('select-all-variables'); - const deselectAllBtn = document.getElementById('deselect-all-variables'); - - if (closeBtn) { - closeBtn.addEventListener('click', () => { - this.hideVariableModal(); - }); - } - - if (cancelBtn) { - cancelBtn.addEventListener('click', () => { - this.hideVariableModal(); - }); - } - - if (confirmBtn) { - confirmBtn.addEventListener('click', () => { - this.confirmVariableSelection(); - }); - } - - if (selectAllBtn) { - selectAllBtn.addEventListener('click', () => { - this.selectAllVisibleVariables(); - }); - } - - if (deselectAllBtn) { - deselectAllBtn.addEventListener('click', () => { - this.deselectAllVisibleVariables(); - }); - } - - // Prevenir cierre con teclas como Ctrl+V, Escape (opcional) - if (modal) { - modal.addEventListener('keydown', (e) => { - // Prevenir cierre accidental con Ctrl+V y otras combinaciones - if (e.ctrlKey || e.metaKey) { - e.stopPropagation(); - } - - // Solo permitir Escape para cerrar si se presiona intencionalmente - if (e.key === 'Escape' && !e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey) { - this.hideVariableModal(); - } - }); - - // Prevenir cierre al hacer clic en el modal (solo en el backdrop) - modal.addEventListener('click', (e) => { - if (e.target === modal) { - this.hideVariableModal(); - } - }); - } - } - - showPlotForm(sessionId = null) { - - // Asegurar que estemos en el tab de plotting - if (typeof tabManager !== 'undefined' && tabManager.getCurrentTab() !== 'plotting') { - tabManager.switchTab('plotting'); - } - - const formContainer = document.getElementById('plot-form-container'); - const formTitle = document.getElementById('plot-form-title'); - const submitBtn = document.getElementById('plot-form-submit'); - - // Verificar que los elementos existan - if (!formContainer) { - console.error('plot-form-container element not found'); - showNotification('Error: Form container not found', 'error'); - return; - } - - if (!formTitle) { - console.warn('plot-form-title element not found'); - } - - if (!submitBtn) { - console.warn('plot-form-submit element not found'); - } - - this.currentEditingSession = sessionId; - - if (sessionId) { - // Modo edición - if (formTitle) formTitle.textContent = '✏️ Edit Plot'; - if (submitBtn) submitBtn.textContent = 'Update Plot'; - this.loadPlotConfigForEdit(sessionId); - } else { - // Modo creación - if (formTitle) formTitle.textContent = '🆕 Create New Plot'; - if (submitBtn) submitBtn.textContent = 'Create Plot'; - this.resetPlotForm(); - } - - formContainer.style.display = 'block'; - - // Scroll hacia el formulario para que sea visible - setTimeout(() => { - formContainer.scrollIntoView({ behavior: 'smooth', block: 'start' }); - }, 100); - - // Cargar variables disponibles para triggers - this.loadTriggerVariables(); - } - - hidePlotForm() { - const formContainer = document.getElementById('plot-form-container'); - formContainer.style.display = 'none'; - this.currentEditingSession = null; - this.selectedVariables.clear(); - this.updateSelectedVariablesDisplay(); - } - - resetPlotForm() { - const form = document.getElementById('plot-form'); - form.reset(); - document.getElementById('plot-form-time-window').value = '60'; - document.getElementById('plot-form-trigger-on-true').checked = true; - this.selectedVariables.clear(); - this.updateSelectedVariablesDisplay(); - } - - async loadPlotConfigForEdit(sessionId) { - try { - const response = await fetch(`/api/plots/${sessionId}/config`); - const data = await response.json(); - - if (data.success && data.config) { - const config = data.config; - - // Llenar formulario - document.getElementById('plot-form-name').value = config.name || ''; - document.getElementById('plot-form-time-window').value = config.time_window || 60; - document.getElementById('plot-form-y-min').value = config.y_min || ''; - document.getElementById('plot-form-y-max').value = config.y_max || ''; - document.getElementById('plot-form-trigger-enabled').checked = config.trigger_enabled || false; - document.getElementById('plot-form-trigger-variable').value = config.trigger_variable || ''; - document.getElementById('plot-form-trigger-on-true').checked = config.trigger_on_true !== false; - - // Configurar trigger - this.togglePlotFormTriggerConfig(); - - // Cargar variables seleccionadas - this.selectedVariables.clear(); - if (config.variables) { - const enabledVariables = this.getEnabledVariables(config.variables); - enabledVariables.forEach((variableInfo) => { - this.selectedVariables.set(variableInfo.name, { - color: variableInfo.color, - dataset: 'Unknown' // Will be resolved when loading datasets - }); - }); - } - this.updateSelectedVariablesDisplay(); - } - } catch (error) { - console.error('Error loading plot config for edit:', error); - showNotification('Error loading plot configuration', 'error'); - } - } - - async showVariableModal() { - const modal = document.getElementById('variable-selection-modal'); - - // Cargar datasets y variables - await this.loadDatasetsAndVariables(); - - modal.style.display = 'block'; - } - - hideVariableModal() { - const modal = document.getElementById('variable-selection-modal'); - modal.style.display = 'none'; - } - - async loadDatasetsAndVariables() { - try { - // Cargar datasets - const response = await fetch('/api/datasets'); - const data = await response.json(); - - if (data.success) { - this.availableDatasets = Object.entries(data.datasets).map(([id, dataset]) => ({ - id: id, - name: dataset.name, - variables: dataset.variables || {}, - active: data.active_datasets.includes(id) - })); - - this.updateDatasetsDisplay(); - } - } catch (error) { - console.error('Error loading datasets:', error); - showNotification('Error loading datasets', 'error'); - } - } - - updateDatasetsDisplay() { - const container = document.getElementById('datasets-list'); - container.innerHTML = ''; - - if (this.availableDatasets.length === 0) { - container.innerHTML = '

No datasets available

'; - return; - } - - for (const dataset of this.availableDatasets) { - const item = document.createElement('div'); - item.className = 'dataset-item'; - item.dataset.datasetId = dataset.id; - - const variableCount = Object.keys(dataset.variables).length; - const statusIcon = dataset.active ? '🟢' : '⚫'; - - item.innerHTML = ` - ${statusIcon} ${dataset.name} - ${variableCount} variables - `; - - item.addEventListener('click', () => { - this.selectDataset(dataset.id); - }); - - container.appendChild(item); - } - } - - selectDataset(datasetId) { - // Actualizar UI - document.querySelectorAll('.dataset-item').forEach(item => { - item.classList.remove('active'); - }); - document.querySelector(`[data-dataset-id="${datasetId}"]`).classList.add('active'); - - // Mostrar variables del dataset - const dataset = this.availableDatasets.find(d => d.id === datasetId); - if (dataset) { - this.updateVariablesDisplay(dataset); - } - } - - updateVariablesDisplay(dataset) { - const container = document.getElementById('variables-list'); - container.innerHTML = ''; - - const variables = Object.entries(dataset.variables); - - if (variables.length === 0) { - container.innerHTML = '

No variables in this dataset

'; - return; - } - - for (const [varName, varConfig] of variables) { - const item = document.createElement('div'); - item.className = 'variable-item'; - item.dataset.variableName = varName; - - const isSelected = this.selectedVariables.has(varName); - const selectedData = this.selectedVariables.get(varName); - const color = selectedData?.color || this.getNextAvailableColor(); - - if (isSelected) { - item.classList.add('selected'); - } - - // Formatear detalles de la variable - let details = `${varConfig.type.toUpperCase()}`; - if (varConfig.area === 'db') { - details += ` - DB${varConfig.db}.${varConfig.offset}`; - } else if (varConfig.area === 'mw') { - details += ` - MW${varConfig.offset}`; - } else if (['e', 'a', 'mb'].includes(varConfig.area)) { - details += ` - ${varConfig.area.toUpperCase()}${varConfig.offset}.${varConfig.bit || 0}`; - } - - item.innerHTML = ` -
-
${varName}
-
${details}
-
-
- - -
- `; - - container.appendChild(item); - } - } - - toggleVariableSelection(variableName, datasetId, selected) { - if (selected) { - const color = this.getNextAvailableColor(); - const datasetName = this.availableDatasets.find(d => d.id === datasetId)?.name || datasetId; - - this.selectedVariables.set(variableName, { - color: color, - dataset: datasetName - }); - - // Habilitar selector de color - const colorSelector = document.querySelector(`[data-variable-name="${variableName}"] .color-selector`); - if (colorSelector) { - colorSelector.disabled = false; - colorSelector.value = color; - } - - // Marcar item como seleccionado - const item = document.querySelector(`[data-variable-name="${variableName}"]`); - if (item) { - item.classList.add('selected'); - } - } else { - this.selectedVariables.delete(variableName); - - // Deshabilitar selector de color - const colorSelector = document.querySelector(`[data-variable-name="${variableName}"] .color-selector`); - if (colorSelector) { - colorSelector.disabled = true; - } - - // Desmarcar item - const item = document.querySelector(`[data-variable-name="${variableName}"]`); - if (item) { - item.classList.remove('selected'); - } - } - - this.updateSelectedSummary(); - } - - updateVariableColor(variableName, color) { - if (this.selectedVariables.has(variableName)) { - const data = this.selectedVariables.get(variableName); - data.color = color; - this.selectedVariables.set(variableName, data); - this.updateSelectedSummary(); - } - } - - selectAllVisibleVariables() { - const checkboxes = document.querySelectorAll('.variables-list .variable-checkbox'); - checkboxes.forEach(checkbox => { - if (!checkbox.checked) { - checkbox.checked = true; - checkbox.onchange(); - } - }); - } - - deselectAllVisibleVariables() { - const checkboxes = document.querySelectorAll('.variables-list .variable-checkbox'); - checkboxes.forEach(checkbox => { - if (checkbox.checked) { - checkbox.checked = false; - checkbox.onchange(); - } - }); - } - - updateSelectedSummary() { - const container = document.getElementById('selected-variables-summary'); - container.innerHTML = ''; - - if (this.selectedVariables.size === 0) { - container.innerHTML = '

No variables selected

'; - return; - } - - for (const [varName, data] of this.selectedVariables) { - const item = document.createElement('div'); - item.className = 'selected-summary-item'; - item.innerHTML = ` - - ${varName} - (${data.dataset}) - `; - container.appendChild(item); - } - } - - confirmVariableSelection() { - this.updateSelectedVariablesDisplay(); - this.hideVariableModal(); - } - - updateSelectedVariablesDisplay() { - const container = document.getElementById('selected-variables-display'); - container.innerHTML = ''; - - if (this.selectedVariables.size === 0) { - container.innerHTML = '

No variables selected

'; - return; - } - - for (const [varName, data] of this.selectedVariables) { - const chip = document.createElement('div'); - chip.className = 'variable-chip'; - chip.innerHTML = ` - - ${varName} - - `; - container.appendChild(chip); - } - } - - removeSelectedVariable(variableName) { - this.selectedVariables.delete(variableName); - this.updateSelectedVariablesDisplay(); - } - - getNextAvailableColor() { - const usedColors = new Set([...this.selectedVariables.values()].map(v => v.color)); - return this.colors.find(color => !usedColors.has(color)) || this.colors[0]; - } - - getEnabledVariables(variables) { - if (Array.isArray(variables)) { - // Backward compatibility: convert old array format to new object format - return variables.map((variable, index) => ({ - name: variable, - color: this.getColor(variable, index), - enabled: true - })); - } else if (variables && typeof variables === 'object') { - // Check if this is the new format with variable_name property - const firstVarConfig = Object.values(variables)[0]; - if (firstVarConfig && 'variable_name' in firstVarConfig) { - // New format: variable_name as property - return Object.entries(variables) - .filter(([id, config]) => config.enabled !== false && config.variable_name) - .map(([id, config]) => ({ - name: config.variable_name, - color: config.color || this.getColor(config.variable_name), - enabled: config.enabled !== false - })); - } else { - // Old object format: variable names as keys - return Object.entries(variables) - .filter(([name, config]) => config.enabled !== false) - .map(([name, config]) => ({ - name: name, - color: config.color || this.getColor(name), - enabled: config.enabled !== false - })); - } - } - return []; - } - - getColor(variable, index = null) { - if (index !== null) { - return this.colors[index % this.colors.length]; - } - const hash = this.hashCode(variable); - return this.colors[hash % this.colors.length]; - } - - hashCode(str) { - let hash = 0; - for (let i = 0; i < str.length; i++) { - const char = str.charCodeAt(i); - hash = ((hash << 5) - hash) + char; - hash = hash & hash; - } - return Math.abs(hash); - } - - async loadTriggerVariables() { - try { - const response = await fetch('/api/plots/variables'); - const data = await response.json(); - - const triggerSelect = document.getElementById('plot-form-trigger-variable'); - triggerSelect.innerHTML = ''; - - if (data.boolean_variables) { - for (const variable of data.boolean_variables) { - const option = document.createElement('option'); - option.value = variable; - option.textContent = variable; - triggerSelect.appendChild(option); - } - } - } catch (error) { - console.error('Error loading trigger variables:', error); - } - } - - async handleFormSubmit() { - if (this.selectedVariables.size === 0) { - showNotification('Please select at least one variable', 'error'); - return; - } - - // Convert selectedVariables to new structure with variable_name property - const variablesConfig = {}; - let varCounter = 1; - this.selectedVariables.forEach((info, variableName) => { - variablesConfig[`var_${varCounter}`] = { - variable_name: variableName, - color: info.color, - enabled: true - }; - varCounter++; - }); - - const config = { - name: document.getElementById('plot-form-name').value || `Plot ${Date.now()}`, - variables: variablesConfig, - time_window: parseInt(document.getElementById('plot-form-time-window').value) || 60, - y_min: document.getElementById('plot-form-y-min').value || null, - y_max: document.getElementById('plot-form-y-max').value || null, - trigger_enabled: document.getElementById('plot-form-trigger-enabled').checked, - trigger_variable: document.getElementById('plot-form-trigger-variable').value || null, - trigger_on_true: document.getElementById('plot-form-trigger-on-true').checked - }; - - // Convertir valores numéricos - if (config.y_min) config.y_min = parseFloat(config.y_min); - if (config.y_max) config.y_max = parseFloat(config.y_max); - - // 🔧 DEBUG: Log configuración del plot - plotDebugLog('📈 Creating plot with config:', config); - - try { - let response; - let sessionId = null; - - if (this.currentEditingSession) { - // Modo edición: actualizar configuración vía PUT sin eliminar la sesión - const sessionIdToUpdate = this.currentEditingSession; - response = await fetch(`/api/plots/${sessionIdToUpdate}/config`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(config) - }); - - const result = await response.json(); - if (result && result.success) { - // Actualizar estado local y aplicar cambios al chart existente - const sessionData = this.sessions.get(sessionIdToUpdate); - if (sessionData) { - sessionData.config = { ...(sessionData.config || {}), ...config }; - this.applyConfigToChart(sessionIdToUpdate, config); - } - - // Actualizar nombre del tab si cambió - if (typeof tabManager !== 'undefined' && config.name) { - tabManager.updatePlotTabName(sessionIdToUpdate, config.name); - tabManager.switchSubTab(`plot-${sessionIdToUpdate}`); - } - - this.updatePlotStats(sessionIdToUpdate, sessionData ? sessionData.config : config); - this.hidePlotForm(); - showNotification(result.message || 'Plot updated', 'success'); - return; - } else { - showNotification((result && result.error) || 'Failed to update plot', 'error'); - return; - } - } else { - // Modo creación normal - response = await fetch('/api/plots', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(config) - }); - - const result = await response.json(); - if (result.success) { - sessionId = result.session_id; - showNotification(result.message, 'success'); - - // Crear nuevo tab para la sesión - await this.createPlotSessionTab(sessionId, { - name: config.name, - variables_count: config.variables.length, - trigger_enabled: config.trigger_enabled, - trigger_variable: config.trigger_variable, - trigger_on_true: config.trigger_on_true, - is_active: false, - is_paused: false - }); - - // Cambiar al sub-tab del nuevo plot - if (typeof tabManager !== 'undefined') { - tabManager.switchSubTab(`plot-${sessionId}`); - } - - this.hidePlotForm(); - } else { - showNotification(result.error, 'error'); - } - } - } catch (error) { - console.error('Error submitting plot form:', error); - showNotification(this.currentEditingSession ? 'Error updating plot' : 'Error creating plot', 'error'); - } - } - - destroy() { - this.stopAutoUpdate(); - - // Destruir todos los charts y limpiar intervalos - for (const [sessionId, sessionData] of this.sessions) { - if (sessionData) { - // Limpiar intervalo manual si existe - if (sessionData.manualInterval) { - clearInterval(sessionData.manualInterval); - } - - // Destruir chart - if (sessionData.chart) { - sessionData.chart.destroy(); - } - } - } - this.sessions.clear(); - } - - /** - * Ajusta el intervalo de adquisición de datos (no el frame rate) - */ - updateRefreshRate(sessionId, refreshRate) { - try { - const refreshRateMs = parseInt(refreshRate); - if (isNaN(refreshRateMs)) return; - const finalRefreshRateMs = Math.min(Math.max(refreshRateMs, 100), 60000); - this.refreshRates.set(sessionId, finalRefreshRateMs); - - const sessionData = this.sessions.get(sessionId); - if (!sessionData || !sessionData.chart) return; - - if (sessionData.isRealTimeMode) { - const chart = sessionData.chart; - if (chart.scales && chart.scales.x && chart.scales.x.realtime) { - chart.scales.x.realtime.refresh = finalRefreshRateMs; - const streaming = chart.$streaming; - if (streaming) { - if (streaming.intervalId) clearInterval(streaming.intervalId); - streaming.intervalId = setInterval(() => { - if (!chart.scales.x.realtime.pause && typeof chart.scales.x.realtime.onRefresh === 'function') { - chart.scales.x.realtime.onRefresh(chart); - } - }, finalRefreshRateMs); - } - chart.options.scales.x.realtime.refresh = finalRefreshRateMs; - } - } else { - this.startManualRefresh(sessionId); - } - } catch (error) { - console.error('❌ Error updating refresh rate:', error); - } - } - - /** - * Actualiza dinámicamente la ventana de tiempo (span) del plot sin recrearlo - */ - updateTimeWindow(sessionId, timeWindowSeconds) { - try { - const seconds = parseInt(timeWindowSeconds); - if (isNaN(seconds)) return; - const clampedSeconds = Math.min(Math.max(seconds, 5), 3600); - - const sessionData = this.sessions.get(sessionId); - if (!sessionData || !sessionData.chart) return; - - // Guardar en la config local - sessionData.config = sessionData.config || {}; - sessionData.config.time_window = clampedSeconds; - - const chart = sessionData.chart; - - if (sessionData.isRealTimeMode) { - // Modo realtime: ajustar duración de la escala en ms - const ms = clampedSeconds * 1000; - if (chart.scales && chart.scales.x && chart.scales.x.realtime) { - chart.scales.x.realtime.duration = ms; - } - if (chart.options && chart.options.scales && chart.options.scales.x && chart.options.scales.x.realtime) { - chart.options.scales.x.realtime.duration = ms; - } - chart.update('quiet'); - } else { - // Modo fallback: limpiar datos fuera de la nueva ventana y refrescar - this.cleanupOldDataFallback(sessionId); - chart.update('quiet'); - } - } catch (error) { - console.error('❌ Error updating time window:', error); - } - } - - /** - * Handler para cambios del control de ventana de tiempo en UI, y persistencia en backend - */ - async onTimeWindowChange(sessionId, timeWindowSeconds) { - this.updateTimeWindow(sessionId, timeWindowSeconds); - try { - const sessionData = this.sessions.get(sessionId); - if (!sessionData) return; - - const baseConfig = sessionData.config || {}; - const payload = { - name: baseConfig.name || `Plot ${sessionId}`, - variables: Array.isArray(baseConfig.variables) ? baseConfig.variables : [], - time_window: parseInt(timeWindowSeconds) || baseConfig.time_window || 60, - y_min: baseConfig.y_min ?? null, - y_max: baseConfig.y_max ?? null, - trigger_enabled: baseConfig.trigger_enabled || false, - trigger_variable: baseConfig.trigger_variable || null, - trigger_on_true: baseConfig.trigger_on_true !== false, - }; - - const response = await fetch(`/api/plots/${sessionId}/config`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(payload) - }); - - const result = await response.json(); - if (result && result.success) { - sessionData.config = { ...baseConfig, ...payload }; - this.updatePlotStats(sessionId, sessionData.config); - showNotification(result.message || 'Plot updated', 'success'); - } else if (result && result.error) { - showNotification(result.error, 'error'); - } - } catch (error) { - console.error('❌ Error persisting time window:', error); - } - } - - /** - * Aplica una configuración de plot al chart existente sin recrearlo - */ - applyConfigToChart(sessionId, config) { - const sessionData = this.sessions.get(sessionId); - if (!sessionData || !sessionData.chart) return; - - const chart = sessionData.chart; - const wasPaused = sessionData.isPaused === true; - - // Pausar ingesta/render en modo realtime para evitar condiciones de carrera - try { - if (sessionData.isRealTimeMode && chart.scales && chart.scales.x && chart.scales.x.realtime) { - chart.scales.x.realtime.pause = true; - } - sessionData.ingestPaused = true; - } catch (_) { /* noop */ } - - // Guardar config local combinada - sessionData.config = { ...(sessionData.config || {}), ...config }; - - // 1) Detectar si cambian las variables/datasets (antes de tocar el chart) - if (Array.isArray(config.variables)) { - const previousLabels = (chart.data.datasets || []).map(ds => ds && ds.label).filter(Boolean); - const changed = previousLabels.length !== config.variables.length || - previousLabels.some((lbl, idx) => lbl !== config.variables[idx]); - - if (changed) { - // Preservar refresh rate para la sesión - const previousRefresh = this.refreshRates.get(sessionId) || 1000; - - // Destruir chart actual para evitar estados inconsistentes del plugin - try { chart.destroy(); } catch (_) { } - - // Re-crear el chart con la nueva configuración sobre el mismo canvas - this.createStreamingChart(sessionId, sessionData.config); - - // Restaurar refresh rate y timers - this.updateRefreshRate(sessionId, previousRefresh); - - // Reaplicar estado de pausa/ingesta sobre el nuevo chart - const newSessionData = this.sessions.get(sessionId); - if (newSessionData) { - newSessionData.isPaused = wasPaused; - newSessionData.ingestPaused = wasPaused; - try { - if (newSessionData.isRealTimeMode && newSessionData.chart && newSessionData.chart.scales && newSessionData.chart.scales.x && newSessionData.chart.scales.x.realtime) { - newSessionData.chart.scales.x.realtime.pause = wasPaused; - } else if (!newSessionData.isRealTimeMode && !wasPaused) { - this.startManualRefresh(sessionId); - } - } catch (_) { /* noop */ } - } - - // Y-axis range ya fue aplicado por createStreamingChart a partir de config - return; - } else { - // Si no cambió el conjunto de variables, solo refrescar estilos - const enabledVariables = this.getEnabledVariables(config.variables); - const newDatasets = enabledVariables.map((variableInfo, index) => { - const color = variableInfo.color || this.getColor(variableInfo.name, index); - const existing = chart.data.datasets[index]; - if (existing) { - existing.label = variableInfo.name; - existing.borderColor = color; - existing.backgroundColor = color + '20'; - existing.borderWidth = 2; - existing.fill = false; - existing.spanGaps = true; - existing.pointRadius = 0; - existing.pointHoverRadius = 3; - existing.tension = 0.1; - return existing; - } - return { - label: variableInfo.name, - data: [], - borderColor: color, - backgroundColor: color + '20', - borderWidth: 2, - fill: false, - spanGaps: true, - pointRadius: 0, - pointHoverRadius: 3, - tension: 0.1 - }; - }); - chart.data.datasets = newDatasets; - - // Recalcular índices y timestamps de empuje - sessionData.datasetIndex = new Map(); - sessionData.lastPushedXByDataset = new Map(); - config.variables.forEach((variable, idx) => sessionData.datasetIndex.set(variable, idx)); - } - } - - // 2) Actualizar ventana de tiempo y rango Y cuando no hubo recreación - if (typeof config.time_window !== 'undefined') { - this.updateTimeWindow(sessionId, config.time_window); - } - if (chart.options && chart.options.scales && chart.options.scales.y) { - chart.options.scales.y.min = config.y_min ?? undefined; - chart.options.scales.y.max = config.y_max ?? undefined; - } - - try { chart.update('quiet'); } catch (_) { } - - // 3) Reanudar ingesta/render si no estaba en pausa antes - try { - sessionData.ingestPaused = wasPaused; - sessionData.isPaused = wasPaused; - if (sessionData.isRealTimeMode && chart.scales && chart.scales.x && chart.scales.x.realtime) { - chart.scales.x.realtime.pause = wasPaused; - } - } catch (_) { /* noop */ } - } -} - -// Funciones de debug removidas para limpiar console.log - -// Función global para toggle de trigger config -window.togglePlotFormTriggerConfig = function () { - const triggerEnabled = document.getElementById('plot-form-trigger-enabled'); - const triggerConfig = document.getElementById('plot-form-trigger-config'); - - if (triggerConfig) { - triggerConfig.style.display = triggerEnabled.checked ? 'block' : 'none'; - } -} - -// Función global corregida para editar plots -window.editPlotSession = function (sessionId) { - if (!sessionId || sessionId.trim() === '') { - console.error('Invalid or empty session ID provided to editPlotSession'); - showNotification('Error: Invalid session ID', 'error'); - return; - } - - if (!plotManager) { - console.error('Plot manager not initialized'); - showNotification('Error: Plot manager not available', 'error'); - return; - } - - plotManager.showPlotForm(sessionId); -} - -// Función global para remover sesiones de plot -window.removePlotSession = async function (sessionId) { - if (confirm('¿Estás seguro de que quieres eliminar este plot?')) { - try { - const response = await fetch(`/api/plots/${sessionId}`, { - method: 'DELETE' - }); - - const result = await response.json(); - - if (result.success) { - // Remover tab dinámico - if (typeof tabManager !== 'undefined') { - tabManager.removePlotTab(sessionId); - } - - // Remover de PlotManager - if (plotManager && plotManager.sessions.has(sessionId)) { - plotManager.sessions.delete(sessionId); - - // Limpiar refresh rate - plotManager.refreshRates.delete(sessionId); - } - - showNotification(result.message, 'success'); - } else { - showNotification(result.error, 'error'); - } - } catch (error) { - console.error('Error removing plot:', error); - showNotification('Error removing plot session', 'error'); - } - } -} - -// Función global para remover sesiones de plot (movida fuera de la clase) -window.removePlotSession = async function (sessionId) { - if (confirm('¿Estás seguro de que quieres eliminar este plot?')) { - try { - const response = await fetch(`/api/plots/${sessionId}`, { - method: 'DELETE' - }); - - const result = await response.json(); - - if (result.success) { - // Remover tab dinámico - if (typeof tabManager !== 'undefined') { - tabManager.removePlotTab(sessionId); - } - - // Remover de PlotManager - if (plotManager && plotManager.sessions.has(sessionId)) { - plotManager.sessions.delete(sessionId); - - // Limpiar refresh rate - plotManager.refreshRates.delete(sessionId); - } - - showNotification(result.message, 'success'); - } else { - showNotification(result.error, 'error'); - } - } catch (error) { - console.error('Error removing plot:', error); - showNotification('Error removing plot session', 'error'); - } - } -} - -// Función de utilidad para notificaciones -window.showNotification = function (message, type = 'info') { - // Si tienes un sistema de notificaciones, úsalo aquí - if (typeof showAlert === 'function') { - showAlert(message, type); - } else if (typeof showMessage === 'function') { - showMessage(message, type); - } -} - -// Inicialización -let plotManager = null; - -function initializePlotManager() { - // Verificar que Chart.js esté cargado - if (typeof Chart === 'undefined') { - console.error('❌ Chart.js not loaded! Retrying in 1000ms...'); - setTimeout(initializePlotManager, 1000); - return; - } - - // Intentar registrar plugin de zoom (UMD) con soporte a default export - try { - // chartjs-plugin-zoom UMD expone global ChartZoom - if (window.ChartZoom) { - try { Chart.register(window.ChartZoom); } catch (_) { } - } - console.log('🔎 Zoom plugin registration attempted. has ChartZoom =', !!window.ChartZoom); - } catch (e) { - console.warn('⚠️ Zoom plugin registration attempt failed:', e); - } - - // Verificar si RealTimeScale está disponible - const hasRealTimeScale = !!Chart.registry.scales.realtime; - - // Exportar clase PlotManager globalmente - window.PlotManager = PlotManager; - - // Inicializar Plot Manager - window.plotManager = new PlotManager(); - - // Para compatibilidad - plotManager = window.plotManager; - - -} - -document.addEventListener('DOMContentLoaded', function () { - // Intentar inicializar inmediatamente - initializePlotManager(); - - // Cerrar modales con Escape (pero prevenir cierre accidental) - document.addEventListener('keydown', function (e) { - if (e.key === 'Escape' && !e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey) { - // Cerrar formulario colapsible si está abierto - const formContainer = document.getElementById('plot-form-container'); - if (formContainer && formContainer.style.display !== 'none' && window.plotManager) { - window.plotManager.hidePlotForm(); - } - } - }); -}); \ No newline at end of file diff --git a/static/js/status.js b/static/js/status.js deleted file mode 100644 index fcb8106..0000000 --- a/static/js/status.js +++ /dev/null @@ -1,299 +0,0 @@ -/** - * Gestión del estado del sistema y actualizaciones en tiempo real - */ - -// Variables para el streaming de estado -let statusEventSource = null; -let isStreamingStatus = false; - -// Actualizar el estado del sistema -function updateStatus() { - fetch('/api/status') - .then(response => response.json()) - .then(data => { - const plcStatus = document.getElementById('plc-status'); - const streamStatus = document.getElementById('stream-status'); - const csvStatus = document.getElementById('csv-status'); - const diskSpaceStatus = document.getElementById('disk-space'); - - // Actualizar estado de conexión PLC con información de reconexión - if (data.plc_connected) { - const reconnectionInfo = data.plc_reconnection || {}; - let reconnectionStatus = ''; - - if (reconnectionInfo.enabled) { - reconnectionStatus = '
🔄 Auto-reconnection: enabled
'; - } - - plcStatus.innerHTML = `🔌 PLC: Connected ${reconnectionStatus}
`; - plcStatus.className = 'status-item status-connected'; - - // Añadir event listener al nuevo botón de desconexión - document.getElementById('status-disconnect-btn').addEventListener('click', function () { - fetch('/api/plc/disconnect', { method: 'POST' }) - .then(response => response.json()) - .then(data => { - showMessage(data.message, data.success ? 'success' : 'error'); - updateStatus(); - }); - }); - } else { - const reconnectionInfo = data.plc_reconnection || {}; - const connectionInfo = data.plc_connection_info || {}; - - let statusText = '🔌 PLC: Disconnected'; - let statusClass = 'status-item status-disconnected'; - let reconnectionDetails = ''; - - // Mostrar información de reconexión si está habilitada - if (reconnectionInfo.enabled) { - if (reconnectionInfo.active) { - statusText = '🔌 PLC: Reconnecting...'; - statusClass = 'status-item status-reconnecting'; - - const nextDelay = reconnectionInfo.next_delay_seconds || 0; - const failures = reconnectionInfo.consecutive_failures || 0; - - if (nextDelay > 0) { - reconnectionDetails = `
🔄 Next attempt in ${nextDelay}s (failure #${failures})
`; - } else { - reconnectionDetails = '
🔄 Attempting reconnection...
'; - } - } else { - if (reconnectionInfo.consecutive_failures > 0) { - reconnectionDetails = `
🔄 Auto-reconnection: enabled (${reconnectionInfo.consecutive_failures} failures)
`; - } else { - reconnectionDetails = '
🔄 Auto-reconnection: enabled
'; - } - } - } - - plcStatus.innerHTML = `${statusText} ${reconnectionDetails}
`; - plcStatus.className = statusClass; - - // Añadir event listener al botón de conexión - document.getElementById('status-connect-btn').addEventListener('click', function () { - fetch('/api/plc/connect', { method: 'POST' }) - .then(response => response.json()) - .then(data => { - const msg = data.error ? `${data.message}: ${data.error}` : data.message; - showMessage(msg, data.success ? 'success' : 'error'); - updateStatus(); - }) - .catch(err => { - showMessage(`Error connecting to PLC: ${err}`, 'error'); - }); - }); - } - - // Actualizar estado de streaming UDP - if (data.streaming) { - streamStatus.innerHTML = '📡 UDP Streaming: Active
'; - streamStatus.className = 'status-item status-streaming'; - - // Añadir event listener al botón de parar streaming UDP - document.getElementById('status-streaming-btn').addEventListener('click', function () { - fetch('/api/udp/streaming/stop', { method: 'POST' }) - .then(response => response.json()) - .then(data => { - showMessage(data.message, data.success ? 'success' : 'error'); - updateStatus(); - }); - }); - } else { - streamStatus.innerHTML = '📡 UDP Streaming: Inactive
'; - streamStatus.className = 'status-item status-idle'; - - // Añadir event listener al botón de iniciar streaming UDP - document.getElementById('status-start-btn').addEventListener('click', function () { - fetch('/api/udp/streaming/start', { method: 'POST' }) - .then(response => response.json()) - .then(data => { - showMessage(data.message, data.success ? 'success' : 'error'); - updateStatus(); - }); - }); - } - - // Actualizar estado de grabación CSV - if (data.csv_recording) { - csvStatus.textContent = `💾 CSV: Recording`; - csvStatus.className = 'status-item status-streaming'; - } else { - csvStatus.textContent = `💾 CSV: Inactive`; - csvStatus.className = 'status-item status-idle'; - } - - // Actualizar estado de espacio en disco - if (data.disk_space_info) { - diskSpaceStatus.innerHTML = `💽 Disk: ${data.disk_space_info.free_space} free
- ⏱️ ~${data.disk_space_info.recording_time_left}`; - diskSpaceStatus.className = 'status-item status-idle'; - } else { - diskSpaceStatus.textContent = '💽 Disk Space: Calculating...'; - diskSpaceStatus.className = 'status-item status-idle'; - } - }) - .catch(error => console.error('Error updating status:', error)); -} - -// Iniciar streaming de estado en tiempo real -function startStatusStreaming() { - if (isStreamingStatus) { - return; - } - - // Cerrar conexión existente si hay alguna - if (statusEventSource) { - statusEventSource.close(); - } - - // Crear nueva conexión EventSource - statusEventSource = new EventSource('/api/stream/status?interval=2.0'); - - statusEventSource.onopen = function (event) { - isStreamingStatus = true; - }; - - statusEventSource.onmessage = function (event) { - try { - const data = JSON.parse(event.data); - - switch (data.type) { - case 'connected': - break; - - case 'status': - // Actualizar estado en tiempo real - updateStatusFromStream(data.status); - break; - - case 'error': - console.error('Status stream error:', data.message); - break; - } - } catch (error) { - console.error('Error parsing status SSE data:', error); - } - }; - - statusEventSource.onerror = function (event) { - console.error('Status stream error:', event); - isStreamingStatus = false; - - // Intentar reconectar después de un retraso - setTimeout(() => { - startStatusStreaming(); - }, 10000); - }; -} - -// Detener streaming de estado en tiempo real -function stopStatusStreaming() { - if (statusEventSource) { - statusEventSource.close(); - statusEventSource = null; - } - isStreamingStatus = false; -} - -// Actualizar estado desde datos de streaming -function updateStatusFromStream(status) { - const plcStatus = document.getElementById('plc-status'); - const streamStatus = document.getElementById('stream-status'); - const csvStatus = document.getElementById('csv-status'); - const diskSpaceStatus = document.getElementById('disk-space'); - - // Actualizar estado de conexión PLC - if (status.plc_connected) { - plcStatus.innerHTML = '🔌 PLC: Connected
'; - plcStatus.className = 'status-item status-connected'; - - // Añadir event listener al nuevo botón de desconexión - const disconnectBtn = document.getElementById('status-disconnect-btn'); - if (disconnectBtn) { - disconnectBtn.addEventListener('click', function () { - fetch('/api/plc/disconnect', { method: 'POST' }) - .then(response => response.json()) - .then(data => { - showMessage(data.message, data.success ? 'success' : 'error'); - updateStatus(); - }); - }); - } - } else { - plcStatus.innerHTML = '🔌 PLC: Disconnected
'; - plcStatus.className = 'status-item status-disconnected'; - - // Añadir event listener al botón de conexión - const connectBtn = document.getElementById('status-connect-btn'); - if (connectBtn) { - connectBtn.addEventListener('click', function () { - fetch('/api/plc/connect', { method: 'POST' }) - .then(response => response.json()) - .then(data => { - const msg = data.error ? `${data.message}: ${data.error}` : data.message; - showMessage(msg, data.success ? 'success' : 'error'); - updateStatus(); - }) - .catch(err => { - showMessage(`Error connecting to PLC: ${err}`, 'error'); - }); - }); - } - } - - // Actualizar estado de streaming UDP - if (status.streaming) { - streamStatus.innerHTML = '📡 UDP Streaming: Active
'; - streamStatus.className = 'status-item status-streaming'; - - // Añadir event listener al botón de parar streaming UDP - const stopBtn = document.getElementById('status-streaming-btn'); - if (stopBtn) { - stopBtn.addEventListener('click', function () { - fetch('/api/udp/streaming/stop', { method: 'POST' }) - .then(response => response.json()) - .then(data => { - showMessage(data.message, data.success ? 'success' : 'error'); - updateStatus(); - }); - }); - } - } else { - streamStatus.innerHTML = '📡 UDP Streaming: Inactive
'; - streamStatus.className = 'status-item status-idle'; - - // Añadir event listener al botón de iniciar streaming UDP - const startBtn = document.getElementById('status-start-btn'); - if (startBtn) { - startBtn.addEventListener('click', function () { - fetch('/api/udp/streaming/start', { method: 'POST' }) - .then(response => response.json()) - .then(data => { - showMessage(data.message, data.success ? 'success' : 'error'); - updateStatus(); - }); - }); - } - } - - // Actualizar estado de grabación CSV - if (status.csv_recording) { - csvStatus.textContent = `💾 CSV: Recording`; - csvStatus.className = 'status-item status-streaming'; - } else { - csvStatus.textContent = `💾 CSV: Inactive`; - csvStatus.className = 'status-item status-idle'; - } - - // Actualizar estado de espacio en disco - if (status.disk_space_info) { - diskSpaceStatus.innerHTML = `💽 Disk: ${status.disk_space_info.free_space} free
- ⏱️ ~${status.disk_space_info.recording_time_left}`; - diskSpaceStatus.className = 'status-item status-idle'; - } else { - diskSpaceStatus.textContent = '💽 Disk Space: Calculating...'; - diskSpaceStatus.className = 'status-item status-idle'; - } -} \ No newline at end of file diff --git a/static/js/streaming.js b/static/js/streaming.js deleted file mode 100644 index 40373be..0000000 --- a/static/js/streaming.js +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Gestión del streaming UDP a PlotJuggler (independiente del recording CSV) - */ - -// Inicializar listeners para el control de streaming UDP -function initStreamingListeners() { - // Iniciar streaming UDP - document.getElementById('start-streaming-btn').addEventListener('click', function () { - fetch('/api/udp/streaming/start', { method: 'POST' }) - .then(response => response.json()) - .then(data => { - showMessage(data.message, data.success ? 'success' : 'error'); - updateStatus(); - }) - .catch(error => { - console.error('Error starting UDP streaming:', error); - showMessage('Error starting UDP streaming', 'error'); - }); - }); - - // Detener streaming UDP - document.getElementById('stop-streaming-btn').addEventListener('click', function () { - fetch('/api/udp/streaming/stop', { method: 'POST' }) - .then(response => response.json()) - .then(data => { - showMessage(data.message, data.success ? 'success' : 'error'); - updateStatus(); - }) - .catch(error => { - console.error('Error stopping UDP streaming:', error); - showMessage('Error stopping UDP streaming', 'error'); - }); - }); - - // Cargar estado de streaming de variables - loadStreamingStatus(); -} - -// Cargar estado de variables en streaming -function loadStreamingStatus() { - fetch('/api/variables/streaming') - .then(response => response.json()) - .then(data => { - if (data.success) { - data.streaming_variables.forEach(varName => { - const checkbox = document.getElementById(`stream-${varName}`); - if (checkbox) { - checkbox.checked = true; - } - }); - } - }) - .catch(error => console.error('Error loading streaming status:', error)); -} \ No newline at end of file diff --git a/static/js/tabs.js b/static/js/tabs.js deleted file mode 100644 index d1087a7..0000000 --- a/static/js/tabs.js +++ /dev/null @@ -1,319 +0,0 @@ -/** - * Tab System Management - * Maneja la navegación entre tabs en la aplicación - */ - -class TabManager { - constructor() { - this.currentTab = 'datasets'; - this.plotTabs = new Set(); // Track dynamic plot tabs - this.init(); - } - - init() { - // Event listeners para los botones de tab estáticos - this.bindStaticTabs(); - - // Inicializar con el tab activo por defecto - this.switchTab(this.currentTab); - } - - bindStaticTabs() { - // Solo bindear tabs estáticos, los dinámicos se bindean al crearlos - document.querySelectorAll('.tab-btn:not([data-plot-id])').forEach(btn => { - btn.addEventListener('click', (e) => { - const tabName = e.target.dataset.tab; - this.switchTab(tabName); - }); - }); - } - - switchTab(tabName) { - // Remover clase active de todos los tabs - document.querySelectorAll('.tab-btn').forEach(btn => { - btn.classList.remove('active'); - }); - - document.querySelectorAll('.tab-content').forEach(content => { - content.classList.remove('active'); - }); - - // Activar el tab seleccionado - const activeBtn = document.querySelector(`[data-tab="${tabName}"]`); - const activeContent = document.getElementById(`${tabName}-tab`); - - if (activeBtn && activeContent) { - activeBtn.classList.add('active'); - activeContent.classList.add('active'); - this.currentTab = tabName; - - // Eventos específicos por tab - this.handleTabSpecificEvents(tabName); - } - } - - createPlotTab(sessionId, plotName) { - // Crear botón de sub-tab dinámico - const subTabBtn = document.createElement('button'); - subTabBtn.className = 'sub-tab-btn plot-sub-tab'; - subTabBtn.dataset.subTab = `plot-${sessionId}`; - subTabBtn.dataset.plotId = sessionId; - subTabBtn.innerHTML = ` - 📈 ${plotName} - × - `; - - // Crear contenido del sub-tab - const subTabContent = document.createElement('div'); - subTabContent.className = 'sub-tab-content plot-sub-tab-content'; - subTabContent.id = `plot-${sessionId}-sub-tab`; - subTabContent.innerHTML = ` -
-
-
- 📈 ${plotName} -
- - -
-
-
- -
-
-

📈 ${plotName}

-
- - - - -
- - - ms -
-
-
-
- - Loading plot information... - -
-
-
-
- - - ms -
-
- - - s -
-
- -
-
- -
-
-
- `; - - // Mostrar sub-tabs si no están visibles - const subTabs = document.getElementById('plot-sub-tabs'); - const plotSessionsContainer = document.getElementById('plot-sessions-container'); - const plotSubContent = document.getElementById('plot-sub-content'); - - if (subTabs.style.display === 'none') { - subTabs.style.display = 'flex'; - plotSessionsContainer.style.display = 'none'; - plotSubContent.style.display = 'block'; - } - - // Agregar sub-tab al contenedor de sub-tabs - subTabs.appendChild(subTabBtn); - - // Agregar contenido del sub-tab - plotSubContent.appendChild(subTabContent); - - // Bind events - subTabBtn.addEventListener('click', (e) => { - if (!e.target.classList.contains('sub-tab-close')) { - this.switchSubTab(`plot-${sessionId}`); - } - }); - - // Close button event - subTabBtn.querySelector('.sub-tab-close').addEventListener('click', (e) => { - e.stopPropagation(); - // Llamar a la función que elimina el plot del backend Y del frontend - if (typeof window.removePlotSession === 'function') { - window.removePlotSession(sessionId); - } else { - console.error('removePlotSession function not available'); - // Fallback: solo remover del frontend - this.removePlotTab(sessionId); - } - }); - - this.plotTabs.add(sessionId); - - return subTabBtn; - } - - switchSubTab(subTabName) { - // Remover clase active de todos los sub-tabs - document.querySelectorAll('.sub-tab-btn').forEach(btn => { - btn.classList.remove('active'); - }); - - document.querySelectorAll('.sub-tab-content').forEach(content => { - content.classList.remove('active'); - }); - - // Activar el sub-tab seleccionado - const activeBtn = document.querySelector(`[data-sub-tab="${subTabName}"]`); - const activeContent = document.getElementById(`${subTabName}-sub-tab`); - - if (activeBtn && activeContent) { - activeBtn.classList.add('active'); - activeContent.classList.add('active'); - - // Eventos específicos por sub-tab - this.handleSubTabSpecificEvents(subTabName); - } - } - - handleSubTabSpecificEvents(subTabName) { - if (subTabName.startsWith('plot-')) { - // Sub-tab de plot individual - cargar datos específicos - const sessionId = subTabName.replace('plot-', ''); - if (typeof plotManager !== 'undefined') { - plotManager.updateSessionData(sessionId); - } - } - } - - removePlotTab(sessionId) { - // Remover sub-tab button - const subTabBtn = document.querySelector(`[data-plot-id="${sessionId}"]`); - if (subTabBtn) { - subTabBtn.remove(); - } - - // Remover sub-tab content - const subTabContent = document.getElementById(`plot-${sessionId}-sub-tab`); - if (subTabContent) { - subTabContent.remove(); - } - - this.plotTabs.delete(sessionId); - - // Si no quedan sub-tabs, mostrar vista inicial - const subTabs = document.getElementById('plot-sub-tabs'); - const plotSessionsContainer = document.getElementById('plot-sessions-container'); - const plotSubContent = document.getElementById('plot-sub-content'); - - if (subTabs.children.length === 0) { - subTabs.style.display = 'none'; - plotSessionsContainer.style.display = 'block'; - plotSubContent.style.display = 'none'; - } - } - - updatePlotTabName(sessionId, newName) { - const subTabBtn = document.querySelector(`[data-plot-id="${sessionId}"]`); - if (subTabBtn) { - subTabBtn.innerHTML = ` - 📈 ${newName} - × - `; - - // Re-bind close event - subTabBtn.querySelector('.sub-tab-close').addEventListener('click', (e) => { - e.stopPropagation(); - // Llamar a la función que elimina el plot del backend Y del frontend - if (typeof window.removePlotSession === 'function') { - window.removePlotSession(sessionId); - } else { - console.error('removePlotSession function not available'); - // Fallback: solo remover del frontend - this.removePlotTab(sessionId); - } - }); - } - - // Actualizar header del contenido - const header = document.querySelector(`#plot-${sessionId}-sub-tab h4`); - if (header) { - header.textContent = `📈 ${newName}`; - } - - const articleHeader = document.querySelector(`#plot-${sessionId}-sub-tab header span`); - if (articleHeader) { - articleHeader.textContent = `📈 ${newName}`; - } - } - - handleTabSpecificEvents(tabName) { - switch (tabName) { - case 'plotting': - // Inicializar plotting si no está inicializado - if (typeof plotManager !== 'undefined' && !plotManager.isInitialized) { - plotManager.init(); - } - break; - - case 'events': - // Cargar eventos si no están cargados - if (typeof loadEvents === 'function') { - loadEvents(); - } - break; - - case 'datasets': - // Actualizar datasets si es necesario - if (typeof loadDatasets === 'function') { - loadDatasets(); - } - break; - } - } - - getCurrentTab() { - return this.currentTab; - } -} - -// Inicialización -let tabManager = null; - -document.addEventListener('DOMContentLoaded', function () { - tabManager = new TabManager(); -}); \ No newline at end of file diff --git a/static/js/theme.js b/static/js/theme.js deleted file mode 100644 index 3bb556c..0000000 --- a/static/js/theme.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Gestión del tema de la aplicación (claro/oscuro/auto) - */ - -// Establecer el tema -function setTheme(theme) { - const html = document.documentElement; - const buttons = document.querySelectorAll('.theme-selector button'); - - // Eliminar clase active de todos los botones - buttons.forEach(btn => btn.classList.remove('active')); - - // Establecer tema - html.setAttribute('data-theme', theme); - - // Añadir clase active al botón seleccionado - document.getElementById(`theme-${theme}`).classList.add('active'); - - // Guardar preferencia en localStorage - localStorage.setItem('theme', theme); -} - -// Cargar tema guardado al cargar la página -function loadTheme() { - const savedTheme = localStorage.getItem('theme') || 'light'; - setTheme(savedTheme); -} - -// Inicializar tema al cargar la página -document.addEventListener('DOMContentLoaded', function () { - loadTheme(); -}); \ No newline at end of file diff --git a/static/js/utils.js b/static/js/utils.js deleted file mode 100644 index 8f9014a..0000000 --- a/static/js/utils.js +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Funciones de utilidad general para la aplicación - */ - -// Función para mostrar mensajes en la interfaz -function showMessage(message, type = 'success') { - const messagesDiv = document.getElementById('messages'); - let alertClass; - - switch (type) { - case 'success': - alertClass = 'alert-success'; - break; - case 'warning': - alertClass = 'alert-warning'; - break; - case 'info': - alertClass = 'alert-info'; - break; - case 'error': - default: - alertClass = 'alert-error'; - break; - } - - messagesDiv.innerHTML = `
${message}
`; - setTimeout(() => { - messagesDiv.innerHTML = ''; - }, 5000); -} - -// Formatear timestamp para los logs -function formatTimestamp(isoString) { - const date = new Date(isoString); - return date.toLocaleString('es-ES', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - second: '2-digit' - }); -} - -// Obtener icono para tipo de evento -function getEventIcon(eventType) { - const icons = { - 'plc_connection': '🔗', - 'plc_connection_failed': '❌', - 'plc_disconnection': '🔌', - 'plc_disconnection_error': '⚠️', - 'streaming_started': '▶️', - 'streaming_stopped': '⏹️', - 'streaming_error': '❌', - 'csv_started': '💾', - 'csv_stopped': '📁', - 'csv_error': '❌', - 'config_change': '⚙️', - 'variable_added': '➕', - 'variable_removed': '➖', - 'application_started': '🚀' - }; - return icons[eventType] || '📋'; -} \ No newline at end of file diff --git a/static/js/variables.js b/static/js/variables.js deleted file mode 100644 index c97da32..0000000 --- a/static/js/variables.js +++ /dev/null @@ -1,319 +0,0 @@ -/** - * Gestión de variables y streaming de valores en tiempo real - */ - -// Variables para el streaming de variables -let variableEventSource = null; -let isStreamingVariables = false; - -// Toggle de campos de variables según el área de memoria -function toggleFields() { - const area = document.getElementById('var-area').value; - const dbField = document.getElementById('db-field'); - const dbInput = document.getElementById('var-db'); - const bitField = document.getElementById('bit-field'); - const typeSelect = document.getElementById('var-type'); - - // Manejar campo DB - if (area === 'db') { - dbField.style.display = 'block'; - dbInput.required = true; - } else { - dbField.style.display = 'none'; - dbInput.required = false; - dbInput.value = 1; // Valor por defecto para áreas no DB - } - - // Manejar campo Bit y restricciones de tipo de datos - if (area === 'e' || area === 'a' || area === 'mb') { - bitField.style.display = 'block'; - // Para áreas de bit, forzar tipo de dato a bool - typeSelect.value = 'bool'; - // Deshabilitar otros tipos de datos para áreas de bit - Array.from(typeSelect.options).forEach(option => { - option.disabled = (option.value !== 'bool'); - }); - } else { - bitField.style.display = 'none'; - // Re-habilitar todos los tipos de datos para áreas no-bit - Array.from(typeSelect.options).forEach(option => { - option.disabled = false; - }); - } -} - -// Toggle de campos de edición de variables -function toggleEditFields() { - const area = document.getElementById('edit-var-area').value; - const dbField = document.getElementById('edit-db-field'); - const dbInput = document.getElementById('edit-var-db'); - const bitField = document.getElementById('edit-bit-field'); - const typeSelect = document.getElementById('edit-var-type'); - - // Manejar campo DB - if (area === 'db') { - dbField.style.display = 'block'; - dbInput.required = true; - } else { - dbField.style.display = 'none'; - dbInput.required = false; - dbInput.value = 1; // Valor por defecto para áreas no DB - } - - // Manejar campo Bit y restricciones de tipo de datos - if (area === 'e' || area === 'a' || area === 'mb') { - bitField.style.display = 'block'; - // Para áreas de bit, forzar tipo de dato a bool - typeSelect.value = 'bool'; - // Deshabilitar otros tipos de datos para áreas de bit - Array.from(typeSelect.options).forEach(option => { - option.disabled = (option.value !== 'bool'); - }); - } else { - bitField.style.display = 'none'; - // Re-habilitar todos los tipos de datos para áreas no-bit - Array.from(typeSelect.options).forEach(option => { - option.disabled = false; - }); - } -} - -// Actualizar streaming para una variable -function toggleStreaming(varName, enabled) { - if (!currentDatasetId) { - showMessage('No dataset selected', 'error'); - return; - } - - fetch(`/api/datasets/${currentDatasetId}/variables/${varName}/streaming`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ enabled: enabled }) - }) - .then(response => response.json()) - .then(data => { - showMessage(data.message, data.success ? 'success' : 'error'); - updateStatus(); // Actualizar contador de variables de streaming - }) - .catch(error => { - console.error('Error toggling streaming:', error); - showMessage('Error updating streaming setting', 'error'); - }); -} - -// Auto-start live display when dataset changes (if PLC is connected) -function autoStartLiveDisplay() { - if (currentDatasetId) { - // Check if PLC is connected by fetching status - fetch('/api/status') - .then(response => response.json()) - .then(status => { - if (status.plc_connected && !isStreamingVariables) { - startVariableStreaming(); - showMessage('Live display started automatically for active dataset', 'info'); - } - }) - .catch(error => { - console.error('Error checking PLC status:', error); - }); - } -} - -// Limpiar todos los valores de variables y establecer mensaje de estado -function clearVariableValues(statusMessage = '--') { - // Encontrar todas las celdas de valor y limpiarlas - const valueCells = document.querySelectorAll('[id^="value-"]'); - valueCells.forEach(cell => { - cell.textContent = statusMessage; - cell.style.color = 'var(--pico-muted-color)'; - }); -} - - - -// Iniciar streaming de variables en tiempo real -function startVariableStreaming() { - if (!currentDatasetId || isStreamingVariables) { - return; - } - - // Cerrar conexión existente si hay alguna - if (variableEventSource) { - variableEventSource.close(); - } - - // Crear nueva conexión EventSource - variableEventSource = new EventSource(`/api/stream/variables?dataset_id=${currentDatasetId}&interval=1.0`); - - variableEventSource.onopen = function (event) { - isStreamingVariables = true; - updateStreamingIndicator(true); - }; - - variableEventSource.onmessage = function (event) { - try { - const data = JSON.parse(event.data); - - switch (data.type) { - case 'connected': - break; - - case 'values': - // Actualizar valores de variables en tiempo real desde caché - updateVariableValuesFromStream(data); - break; - - case 'cache_error': - console.error('Cache error in variable stream:', data.message); - showMessage(`Cache error: ${data.message}`, 'error'); - clearVariableValues('CACHE ERROR'); - break; - - case 'plc_disconnected': - clearVariableValues('PLC OFFLINE'); - showMessage('PLC disconnected - cache not being populated', 'warning'); - break; - - case 'dataset_inactive': - clearVariableValues('DATASET INACTIVE'); - showMessage('Dataset is not active - activate to populate cache', 'warning'); - break; - - case 'no_variables': - clearVariableValues('NO VARIABLES'); - showMessage('No variables defined in this dataset', 'info'); - break; - - case 'no_cache': - clearVariableValues('READING...'); - const samplingInfo = data.sampling_interval ? ` (every ${data.sampling_interval}s)` : ''; - showMessage(`Waiting for cache to be populated${samplingInfo}`, 'info'); - break; - - case 'stream_error': - console.error('SSE stream error:', data.message); - showMessage(`Streaming error: ${data.message}`, 'error'); - break; - - default: - break; - } - } catch (error) { - console.error('Error parsing SSE data:', error); - } - }; - - variableEventSource.onerror = function (event) { - console.error('Variable stream error:', event); - isStreamingVariables = false; - updateStreamingIndicator(false); - - // Intentar reconectar después de un retraso - setTimeout(() => { - if (currentDatasetId) { - startVariableStreaming(); - } - }, 5000); - }; -} - -// Detener streaming de variables en tiempo real -function stopVariableStreaming() { - if (variableEventSource) { - variableEventSource.close(); - variableEventSource = null; - } - isStreamingVariables = false; - updateStreamingIndicator(false); -} - -// Actualizar valores de variables desde datos de streaming -function updateVariableValuesFromStream(data) { - const values = data.values; - const timestamp = data.timestamp; - const source = data.source; - const stats = data.stats; - - // Actualizar cada valor de variable - Object.keys(values).forEach(varName => { - const valueCell = document.getElementById(`value-${varName}`); - if (valueCell) { - const value = values[varName]; - valueCell.textContent = value; - - // Código de color basado en el estado del valor - if (value === 'ERROR' || value === 'FORMAT_ERROR') { - valueCell.style.color = 'var(--pico-color-red-500)'; - valueCell.style.fontWeight = 'bold'; - } else { - valueCell.style.color = 'var(--pico-color-green-600)'; - valueCell.style.fontWeight = 'bold'; - } - } - }); - - // Actualizar timestamp e información de origen - const lastRefreshTime = document.getElementById('last-refresh-time'); - if (lastRefreshTime) { - const sourceIcon = source === 'cache' ? '📊' : '🔗'; - const sourceText = source === 'cache' ? 'streaming cache' : 'direct PLC'; - - if (stats && stats.failed > 0) { - lastRefreshTime.innerHTML = ` - 🔄 Live streaming
- - ⚠️ ${stats.success}/${stats.total} variables (${stats.failed} failed) -
- - ${sourceIcon} ${sourceText} • ${new Date(timestamp).toLocaleTimeString()} - - `; - } else { - lastRefreshTime.innerHTML = ` - 🔄 Live streaming
- - ✅ All ${stats ? stats.success : 'N/A'} variables OK -
- - ${sourceIcon} ${sourceText} • ${new Date(timestamp).toLocaleTimeString()} - - `; - } - } -} - -// Actualizar indicador de streaming -function updateStreamingIndicator(isStreaming) { - const toggleBtn = document.getElementById('toggle-streaming-btn'); - if (toggleBtn) { - if (isStreaming) { - toggleBtn.innerHTML = '⏹️ Stop Live Display'; - toggleBtn.title = 'Stop live variable display'; - } else { - toggleBtn.innerHTML = '▶️ Start Live Display'; - toggleBtn.title = 'Start live variable display'; - } - } -} - -// Alternar streaming en tiempo real -function toggleRealTimeStreaming() { - if (isStreamingVariables) { - stopVariableStreaming(); - showMessage('Real-time streaming stopped', 'info'); - } else { - startVariableStreaming(); - showMessage('Real-time streaming started', 'success'); - } - - // Actualizar texto del botón - const toggleBtn = document.getElementById('toggle-streaming-btn'); - if (toggleBtn) { - if (isStreamingVariables) { - toggleBtn.innerHTML = '⏹️ Stop Live Streaming'; - } else { - toggleBtn.innerHTML = '▶️ Start Live Streaming'; - } - } -} - diff --git a/system_state.json b/system_state.json index 37dd2df..3c507fa 100644 --- a/system_state.json +++ b/system_state.json @@ -1,13 +1,13 @@ { "last_state": { "should_connect": true, - "should_stream": false, + "should_stream": true, "active_datasets": [ - "Test", "Fast", + "Test", "DAR" ] }, "auto_recovery_enabled": true, - "last_update": "2025-08-14T13:31:23.906177" + "last_update": "2025-08-14T14:47:13.218027" } \ No newline at end of file diff --git a/templates/index.html b/templates/index.html deleted file mode 100644 index 3a19bc5..0000000 --- a/templates/index.html +++ /dev/null @@ -1,825 +0,0 @@ - - - - - - - - PLC S7-315 Streamer & Logger - - - - - - - - - - - - -
- - - -
- -
-
-

Notice: Esta UI ha sido reemplazada por la SPA React. Accede a /app. - Esta página no se usa en producción.

-
- -
-

- - -- PLC S7-31x Streamer & Logger -

-

Real-time monitoring and streaming system

-
- - -
-
- 🔌 PLC: Disconnected -
- -
-
-
- 📡 UDP Streaming: Inactive -
- -
-
-
- 📊 Datasets: {{ status.datasets_count }} ({{ status.active_datasets_count }} active) -
-
- 📋 Variables: {{ status.total_variables }} -
-
- ⏱️ Interval: {{ status.sampling_interval }}s -
-
- 💾 CSV: Inactive -
-
- 💽 Disk Space: Calculating... -
-
- - -
- - -
- -
-
⚙️ PLC S7-315 Configuration
-
-
- - - -
-
- - - -
-
-
- - -
-
🌐 UDP Gateway Configuration (PlotJuggler)
-
-
- - - -
-
- - -
-
-
-
- - - - - -
- -
-
-
- 📊 Dataset & Variables Management -
- - - -
-
-
- - - - - - - - -
-

📊 Please select a dataset to manage its variables

-

Or create a new dataset to get started

-
-
- - -
-
📡 PlotJuggler UDP Streaming Control
-
-

📡 UDP Streaming: Sends only variables marked for streaming to PlotJuggler via - UDP -

-

💾 Automatic Recording: When PLC is connected, all datasets with variables - automatically record to CSV files

-

📁 File Organization: records/[dd-mm-yyyy]/[prefix]_[hour].csv (e.g., - temp_14.csv, - pressure_14.csv)

-

⏱️ Independent Operation: CSV recording works independently of UDP streaming - - always active when PLC is connected

-
-
- - - -
-
- - -
-
📁 CSV Recording Configuration
-
-

📂 Directory Management: Configure where CSV files are saved and manage file - rotation

-

🔄 Automatic Cleanup: Set limits by size, days, or hours to automatically delete - old - files

-

💿 Disk Space: Monitor available space and estimated recording time remaining -

-
- - -
-
-
- 📁 Records Directory: - Loading... -
-
- 🔄 Rotation Enabled: - Loading... -
-
- 📊 Max Size: - Loading... -
-
- 📅 Max Days: - Loading... -
-
- ⏰ Max Hours: - Loading... -
-
- 🧹 Cleanup Interval: - Loading... -
-
-
- - -
-
- 📊 Directory Information -
-

Loading directory information...

-
-
-
- - -
- ⚙️ Modify Configuration -
-
-
- - - Base directory where CSV files will be saved -
-
- - Automatically delete old files based on limits below -
-
- -
-
- - - Maximum total size of all CSV files in MB (leave empty for no limit) -
-
- - - Delete files older than this many days (leave empty for no limit) -
-
- -
-
- - - Delete files older than this many hours (overrides days setting) -
-
- - - How often to run automatic cleanup -
-
- -
- - - -
-
-
-
-
- - -
-
-
🧩 Dynamic JSON Config Editor
-
-

Esquemas: Selecciona un esquema y edita PLC, Datasets o Plot Sessions con - formularios dinámicos.

-

Import/Export: Puedes importar desde un archivo JSON o exportar el actual.

-
- -
-
- - -
-
- - - -
-
- -
-
-
- - -
-
-
-
- 📈 Real-Time Plotting - -
-
- - - - - - - - - - -
- -
- - -
-
-

📈 No plot sessions created yet

-

Click "New Plot" to create your first real-time chart

-
-
-
-
- - -
-
-
📋 Events & System Logs
-
-

📋 Event Logging: Monitor system events, errors, and operational status

-

🔍 Real-time Updates: Events are automatically updated as they occur

-

📊 Filtering: Filter events by type and time range

-
- -
- - -
- Loading... events -
-
- -
-

Loading events...

-
-
-
- -
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/web_test/PLC S7-315 Streamer & Logger.html b/web_test/PLC S7-315 Streamer & Logger.html deleted file mode 100644 index 934860f..0000000 --- a/web_test/PLC S7-315 Streamer & Logger.html +++ /dev/null @@ -1,986 +0,0 @@ - - - - - - - PLC S7-315 Streamer & Logger - - - - - - - - - - -
- - - -
- -
- -
-

- - -- PLC S7-31x Streamer & Logger -

-

Real-time monitoring and streaming system

-
- - -
-
🔌 PLC: Connected
-
📡 UDP Streaming: Inactive
-
- 📊 Datasets: 2 (1 active) -
-
- 📋 Variables: 6 -
-
- ⏱️ Interval: 0.1s -
-
💾 CSV: Recording
-
💽 Disk: 795.0 GB free
- ⏱️ ~51939.0 days
-
- - -
- - -
- -
-
⚙️ PLC S7-315 Configuration
-
-
- - - -
-
- - - -
-
-
- - -
-
🌐 UDP Gateway Configuration (PlotJuggler)
-
-
- - - -
-
- - -
-
-
-
- - - - - -
- -
-
-
- 📊 Dataset & Variables Management -
- - - -
-
-
- - -
-
-
-
- 📂 Name: DAR - 🏷️ Prefix: dar - ⏱️ Sampling: 0.2s - 📊 Variables: 6 - 📡 Streaming: 4 -
-
- 🟢 Active - - -
-
-
-
- - -
- -
-
-
- 📊 Automatic Variable Monitoring -
- - Variables are automatically monitored and recorded when PLC is connected and dataset - is - active - -
-
- -
-
-
- 🔄 Live streaming
- - ✅ All 6 variables OK -
- - 📊 streaming cache • 5:15:20 p.m. - -
-
- - -
-
- - - - - - -
- -
- - -
-

📊 Variables in Dataset

-
-
- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameMemory AreaOffsetTypeCurrent ValueStream to PlotJugglerActions
CTS306_PVDB1011.13281328REAL322.049 - - - - -
UR29_BrixDB1011.13221322REAL54.258 - - - - -
UR29_Brix_DigitalDB2120.4040REAL0.333 - - - - -
UR29_PEWPEW304304WORD92 - - - - -
UR62_BrixDB1011.12961296REAL0.271 - - - - -
UR62_PEWPEW300300WORD27232 - - - - -
-
- - - -
- - -
-
📡 PlotJuggler UDP Streaming Control
-
-

📡 UDP Streaming: Sends only variables marked for streaming to PlotJuggler via - UDP -

-

💾 Automatic Recording: When PLC is connected, all datasets with variables - automatically record to CSV files

-

📁 File Organization: records/[dd-mm-yyyy]/[prefix]_[hour].csv (e.g., - temp_14.csv, - pressure_14.csv)

-

⏱️ Independent Operation: CSV recording works independently of UDP streaming - - always active when PLC is connected

-
-
- - - -
-
- - -
-
📁 CSV Recording Configuration
-
-

📂 Directory Management: Configure where CSV files are saved and manage file - rotation

-

🔄 Automatic Cleanup: Set limits by size, days, or hours to automatically delete - old - files

-

💿 Disk Space: Monitor available space and estimated recording time remaining -

-
- - -
-
-
- 📁 Records Directory: - D:\Proyectos\Scripts\Siemens\S7_snap7_Stremer_n_Log\records -
-
- 🔄 Rotation Enabled: - ✅ Yes -
-
- 📊 Max Size: - 1000 MB -
-
- 📅 Max Days: - 30 days -
-
- ⏰ Max Hours: - No limit -
-
- 🧹 Cleanup Interval: - 24 hours -
-
-
- - -
-
- 📊 Directory Information -
-
- 📁 Directory: - D:\Proyectos\Scripts\Siemens\S7_snap7_Stremer_n_Log\records -
-
- 📊 Total Files: - 33 -
-
- 💾 Total Size: - 19.99 MB -
- -
- 📅 Oldest File: - 17/7/2025, 1:59:59 p.m. -
- -
- 🆕 Newest File: - 21/7/2025, 5:01:28 p.m. -
-

📂 Day Folders:

-
- 21-07-2025 - 8 files, 4.8 MB -
- -
- 20-07-2025 - 2 files, 0.4 MB -
- -
- 19-07-2025 - 2 files, 0.3 MB -
- -
- 18-07-2025 - 7 files, 5.7 MB -
- -
- 17-07-2025 - 14 files, 8.8 MB -
-
-
-
- - -
- ⚙️ Modify Configuration -
-
-
- - - Base directory where CSV files will be saved -
-
- - Automatically delete old files based on limits below -
-
- -
-
- - - Maximum total size of all CSV files in MB (leave empty for no limit) -
-
- - - Delete files older than this many days (leave empty for no limit) -
-
- -
-
- - - Delete files older than this many hours (overrides days setting) -
-
- - - How often to run automatic cleanup -
-
- -
- - - -
-
-
-
-
- - -
-
-
-
- 📈 Real-Time Plotting - -
-
- - - - - - - - - - -
- -
-
-
-
- 📈 Brix -
- - -
-
-
- -
-
-

📈 Brix

-
- - - - -
-
-
- - Variables: 1 | - Data Points: 0 | - Status: Active | - No Trigger - -
-
- -
-
-
-
- - - -
-
- - -
-
-
📋 Events & System Logs
-
-

📋 Event Logging: Monitor system events, errors, and operational status

-

🔍 Real-time Updates: Events are automatically updated as they occur

-

📊 Filtering: Filter events by type and time range

-
- -
- - -
- Loading... events -
-
- -
-

Loading events...

-
-
-
- -
- - - - - - - - -
-
-
📋 Events & System Logs
-
-

📋 Event Logging: Monitor system events, errors, and operational status

-

🔍 Real-time Updates: Events are automatically updated as they occur

-

📊 Filtering: Filter events by type and time range

-
- -
- - -
- Loading... events -
-
- -
-

Loading events...

-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/web_test/PLC S7-315 Streamer & Logger_files/SIDEL.png b/web_test/PLC S7-315 Streamer & Logger_files/SIDEL.png deleted file mode 100644 index b533bfd6bea2e2c404a8b752017ffc8e8cef06e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 83332 zcmYhi1yozz@;;11ad#^e_hQA1l>)`x-QC^Yo#O6V+}(p0*I>alxPQF&z5nn2vd&7f z&e}PdJ@f3DJu}aake8J}K_o%dbeI0OTB z^3PrfpT@qV-!gZpzPeO&T&$;CkN%=&Pr44-shQhoX+KuZ=5?8x4MzWds!r@P@vh^v z#?pA#+xy$w!%>tq@I?O*8GU+I03$(-4hMmuSHs?O6RXQxm%4$TZ-pvlD zGQ)p{e2kJJX8YcD-hNV|mCA{j3oi^|*LAx{L+WX=p{vaPe+Kn_J1>A|u?*5V8Nd5^ zI598MW(&i}yy~UvJTQKsboTFGqQlTR3_|QIsDOr_&pR&4KIUD~Pzc?7{ry4EyyR09 zF4OAk{k?z-?E>b1g^b6PKt`Wx-|0@5-6*PK%7;%6Re(@c-Si+5<3*Va1v(GuILYZP zqgqtZ{XYd!#V${+-=0=J_q>3|pT7<#lWCV%qd-t54l797tcBt*sGeTWh)$CUH;MmO zDrhn(HrBy(_hp{i+8ejkBr6C=rv)$K=m#aaR0!+7ykR6&LFbXN+?9@;>Hd48x^o-8Wfm07QA5+T~pD#oy}EX~q&!&t%je`G?fiod^`V7N$Iv53yu z3PkDt6M!URX-%jOS!M`BZEk>iGx5{h#KZk2Bz548-|iJs+3SDmot@VHw!4pHX=Oz; z18C-4SNzoIOt^PXPk^{Df7+~}rI!F56DqTDdF3}42$e-? z&5q)9C3EiFps1e;QH*%{B2WrIa zWlg^Kx3Yx1+9;=0D8CpOU}cpeGa>ZMD(nXmn0f<}WBiT+a|ltZ<)N8YM+m#0*G5y)MgjRA^5nLG`G)k zR#r+#^?!ak_}C)k_0k?F0N@IPqOAN&7nAT74#ig!_nQ3FvgUp~@Zo5`JlW3CuFSv>ZF?=Rie7&wwtg;i75H+D;Waow9b3{(u|;$^AbJ*jFGP<=V49 z#Ur7}g!S)J27CP#k&z+q1g=&`Hg%84)>WnGiYH~^rRdnJf0hsUQ-#lLJh1$l5he03 zov%c{Mx{m4QRv=^Z?QSFwh7Yq{qjft`=W>2iV|AZ{gosEt)k8Ud+=QdqeDUez0w7^J&_b!*H%b--_FkKP&puMJkKLz9jnF?m#BeN$Tg`unM944vE#AWuIE8I4 za|1XI+j1!IbH=&<7PJ57y+URr=(&0o4Bp=(bA1)%L7Y_3IUa?#@j#)TefbKX|GAF` z?zX4n6FHOt);K{cA_bhPAI}i8bhCk_Rqg^(U_nigV;k|)B&W91Bxg_FD$W0vf?rU_ zruo|YodljA3#$-RJis494M&FF?gwoFzkNNzq~e=xulD0=1fxSef-dE4CvtEGb5D>2 z!okfErUzfG%vRdfI18XLD18 zxZ|6BUs{Z+ZjzE8OQ_((kp*X zgG&Oa=S6xKN8%DHO3ZyW)=sI(JYx`->#Aid@2zY7^m9^13@GY@{(AJsJRjoay3r

uN&U* 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�}3jEF!d9z3pk>c#a`4;-ZnM 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

p}Z);14a|-)2h} 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 diff --git a/web_test/PLC S7-315 Streamer & Logger_files/chart.js.descargar b/web_test/PLC S7-315 Streamer & Logger_files/chart.js.descargar deleted file mode 100644 index 7eec4e6..0000000 --- a/web_test/PLC S7-315 Streamer & Logger_files/chart.js.descargar +++ /dev/null @@ -1,14 +0,0 @@ -/*! - * Chart.js v4.5.0 - * https://www.chartjs.org - * (c) 2025 Chart.js Contributors - * Released under the MIT License - */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).Chart=e()}(this,(function(){"use strict";var t=Object.freeze({__proto__:null,get Colors(){return Jo},get Decimation(){return ta},get Filler(){return ba},get Legend(){return Ma},get SubTitle(){return Pa},get Title(){return ka},get Tooltip(){return Na}});function e(){}const i=(()=>{let t=0;return()=>t++})();function s(t){return null==t}function n(t){if(Array.isArray&&Array.isArray(t))return!0;const e=Object.prototype.toString.call(t);return"[object"===e.slice(0,7)&&"Array]"===e.slice(-6)}function o(t){return null!==t&&"[object Object]"===Object.prototype.toString.call(t)}function a(t){return("number"==typeof t||t instanceof Number)&&isFinite(+t)}function r(t,e){return a(t)?t:e}function l(t,e){return void 0===t?e:t}const h=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100:+t/e,c=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100*e:+t;function d(t,e,i){if(t&&"function"==typeof t.call)return t.apply(i,e)}function u(t,e,i,s){let a,r,l;if(n(t))if(r=t.length,s)for(a=r-1;a>=0;a--)e.call(i,t[a],a);else for(a=0;at,x:t=>t.x,y:t=>t.y};function v(t){const e=t.split("."),i=[];let s="";for(const t of e)s+=t,s.endsWith("\\")?s=s.slice(0,-1)+".":(i.push(s),s="");return i}function M(t,e){const i=y[e]||(y[e]=function(t){const e=v(t);return t=>{for(const i of e){if(""===i)break;t=t&&t[i]}return t}}(e));return i(t)}function w(t){return t.charAt(0).toUpperCase()+t.slice(1)}const k=t=>void 0!==t,S=t=>"function"==typeof t,P=(t,e)=>{if(t.size!==e.size)return!1;for(const i of t)if(!e.has(i))return!1;return!0};function D(t){return"mouseup"===t.type||"click"===t.type||"contextmenu"===t.type}const C=Math.PI,O=2*C,A=O+C,T=Number.POSITIVE_INFINITY,L=C/180,E=C/2,R=C/4,I=2*C/3,z=Math.log10,F=Math.sign;function V(t,e,i){return Math.abs(t-e)t-e)).pop(),e}function N(t){return!function(t){return"symbol"==typeof t||"object"==typeof t&&null!==t&&!(Symbol.toPrimitive in t||"toString"in t||"valueOf"in t)}(t)&&!isNaN(parseFloat(t))&&isFinite(t)}function H(t,e){const i=Math.round(t);return i-e<=t&&i+e>=t}function j(t,e,i){let s,n,o;for(s=0,n=t.length;sl&&h=Math.min(e,i)-s&&t<=Math.max(e,i)+s}function et(t,e,i){i=i||(i=>t[i]1;)s=o+n>>1,i(s)?o=s:n=s;return{lo:o,hi:n}}const it=(t,e,i,s)=>et(t,i,s?s=>{const n=t[s][e];return nt[s][e]et(t,i,(s=>t[s][e]>=i));function nt(t,e,i){let s=0,n=t.length;for(;ss&&t[n-1]>i;)n--;return s>0||n{const i="_onData"+w(e),s=t[e];Object.defineProperty(t,e,{configurable:!0,enumerable:!1,value(...e){const n=s.apply(this,e);return t._chartjs.listeners.forEach((t=>{"function"==typeof t[i]&&t[i](...e)})),n}})})))}function rt(t,e){const i=t._chartjs;if(!i)return;const s=i.listeners,n=s.indexOf(e);-1!==n&&s.splice(n,1),s.length>0||(ot.forEach((e=>{delete t[e]})),delete t._chartjs)}function lt(t){const e=new Set(t);return e.size===t.length?t:Array.from(e)}const ht="undefined"==typeof window?function(t){return t()}:window.requestAnimationFrame;function ct(t,e){let i=[],s=!1;return function(...n){i=n,s||(s=!0,ht.call(window,(()=>{s=!1,t.apply(e,i)})))}}function dt(t,e){let i;return function(...s){return e?(clearTimeout(i),i=setTimeout(t,e,s)):t.apply(this,s),e}}const ut=t=>"start"===t?"left":"end"===t?"right":"center",ft=(t,e,i)=>"start"===t?e:"end"===t?i:(e+i)/2,gt=(t,e,i,s)=>t===(s?"left":"right")?i:"center"===t?(e+i)/2:e;function pt(t,e,i){const n=e.length;let o=0,a=n;if(t._sorted){const{iScale:r,vScale:l,_parsed:h}=t,c=t.dataset&&t.dataset.options?t.dataset.options.spanGaps:null,d=r.axis,{min:u,max:f,minDefined:g,maxDefined:p}=r.getUserBounds();if(g){if(o=Math.min(it(h,d,u).lo,i?n:it(e,d,r.getPixelForValue(u)).lo),c){const t=h.slice(0,o+1).reverse().findIndex((t=>!s(t[l.axis])));o-=Math.max(0,t)}o=Z(o,0,n-1)}if(p){let t=Math.max(it(h,r.axis,f,!0).hi+1,i?0:it(e,d,r.getPixelForValue(f),!0).hi+1);if(c){const e=h.slice(t-1).findIndex((t=>!s(t[l.axis])));t+=Math.max(0,e)}a=Z(t,o,n)-o}else a=n-o}return{start:o,count:a}}function mt(t){const{xScale:e,yScale:i,_scaleRanges:s}=t,n={xmin:e.min,xmax:e.max,ymin:i.min,ymax:i.max};if(!s)return t._scaleRanges=n,!0;const o=s.xmin!==e.min||s.xmax!==e.max||s.ymin!==i.min||s.ymax!==i.max;return Object.assign(s,n),o}class xt{constructor(){this._request=null,this._charts=new Map,this._running=!1,this._lastDate=void 0}_notify(t,e,i,s){const n=e.listeners[s],o=e.duration;n.forEach((s=>s({chart:t,initial:e.initial,numSteps:o,currentStep:Math.min(i-e.start,o)})))}_refresh(){this._request||(this._running=!0,this._request=ht.call(window,(()=>{this._update(),this._request=null,this._running&&this._refresh()})))}_update(t=Date.now()){let e=0;this._charts.forEach(((i,s)=>{if(!i.running||!i.items.length)return;const n=i.items;let o,a=n.length-1,r=!1;for(;a>=0;--a)o=n[a],o._active?(o._total>i.duration&&(i.duration=o._total),o.tick(t),r=!0):(n[a]=n[n.length-1],n.pop());r&&(s.draw(),this._notify(s,i,t,"progress")),n.length||(i.running=!1,this._notify(s,i,t,"complete"),i.initial=!1),e+=n.length})),this._lastDate=t,0===e&&(this._running=!1)}_getAnims(t){const e=this._charts;let i=e.get(t);return i||(i={running:!1,initial:!0,items:[],listeners:{complete:[],progress:[]}},e.set(t,i)),i}listen(t,e,i){this._getAnims(t).listeners[e].push(i)}add(t,e){e&&e.length&&this._getAnims(t).items.push(...e)}has(t){return this._getAnims(t).items.length>0}start(t){const e=this._charts.get(t);e&&(e.running=!0,e.start=Date.now(),e.duration=e.items.reduce(((t,e)=>Math.max(t,e._duration)),0),this._refresh())}running(t){if(!this._running)return!1;const e=this._charts.get(t);return!!(e&&e.running&&e.items.length)}stop(t){const e=this._charts.get(t);if(!e||!e.items.length)return;const i=e.items;let s=i.length-1;for(;s>=0;--s)i[s].cancel();e.items=[],this._notify(t,e,Date.now(),"complete")}remove(t){return this._charts.delete(t)}}var bt=new xt; -/*! - * @kurkle/color v0.3.2 - * https://github.com/kurkle/color#readme - * (c) 2023 Jukka Kurkela - * Released under the MIT License - */function _t(t){return t+.5|0}const yt=(t,e,i)=>Math.max(Math.min(t,i),e);function vt(t){return yt(_t(2.55*t),0,255)}function Mt(t){return yt(_t(255*t),0,255)}function wt(t){return yt(_t(t/2.55)/100,0,1)}function kt(t){return yt(_t(100*t),0,100)}const St={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:10,B:11,C:12,D:13,E:14,F:15,a:10,b:11,c:12,d:13,e:14,f:15},Pt=[..."0123456789ABCDEF"],Dt=t=>Pt[15&t],Ct=t=>Pt[(240&t)>>4]+Pt[15&t],Ot=t=>(240&t)>>4==(15&t);function At(t){var e=(t=>Ot(t.r)&&Ot(t.g)&&Ot(t.b)&&Ot(t.a))(t)?Dt:Ct;return t?"#"+e(t.r)+e(t.g)+e(t.b)+((t,e)=>t<255?e(t):"")(t.a,e):void 0}const Tt=/^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;function Lt(t,e,i){const s=e*Math.min(i,1-i),n=(e,n=(e+t/30)%12)=>i-s*Math.max(Math.min(n-3,9-n,1),-1);return[n(0),n(8),n(4)]}function Et(t,e,i){const s=(s,n=(s+t/60)%6)=>i-i*e*Math.max(Math.min(n,4-n,1),0);return[s(5),s(3),s(1)]}function Rt(t,e,i){const s=Lt(t,1,.5);let n;for(e+i>1&&(n=1/(e+i),e*=n,i*=n),n=0;n<3;n++)s[n]*=1-e-i,s[n]+=e;return s}function It(t){const e=t.r/255,i=t.g/255,s=t.b/255,n=Math.max(e,i,s),o=Math.min(e,i,s),a=(n+o)/2;let r,l,h;return n!==o&&(h=n-o,l=a>.5?h/(2-n-o):h/(n+o),r=function(t,e,i,s,n){return t===n?(e-i)/s+(e>16&255,o>>8&255,255&o]}return t}(),Ht.transparent=[0,0,0,0]);const e=Ht[t.toLowerCase()];return e&&{r:e[0],g:e[1],b:e[2],a:4===e.length?e[3]:255}}const $t=/^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;const Yt=t=>t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055,Ut=t=>t<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4);function Xt(t,e,i){if(t){let s=It(t);s[e]=Math.max(0,Math.min(s[e]+s[e]*i,0===e?360:1)),s=Ft(s),t.r=s[0],t.g=s[1],t.b=s[2]}}function qt(t,e){return t?Object.assign(e||{},t):t}function Kt(t){var e={r:0,g:0,b:0,a:255};return Array.isArray(t)?t.length>=3&&(e={r:t[0],g:t[1],b:t[2],a:255},t.length>3&&(e.a=Mt(t[3]))):(e=qt(t,{r:0,g:0,b:0,a:1})).a=Mt(e.a),e}function Gt(t){return"r"===t.charAt(0)?function(t){const e=$t.exec(t);let i,s,n,o=255;if(e){if(e[7]!==i){const t=+e[7];o=e[8]?vt(t):yt(255*t,0,255)}return i=+e[1],s=+e[3],n=+e[5],i=255&(e[2]?vt(i):yt(i,0,255)),s=255&(e[4]?vt(s):yt(s,0,255)),n=255&(e[6]?vt(n):yt(n,0,255)),{r:i,g:s,b:n,a:o}}}(t):Bt(t)}class Jt{constructor(t){if(t instanceof Jt)return t;const e=typeof t;let i;var s,n,o;"object"===e?i=Kt(t):"string"===e&&(o=(s=t).length,"#"===s[0]&&(4===o||5===o?n={r:255&17*St[s[1]],g:255&17*St[s[2]],b:255&17*St[s[3]],a:5===o?17*St[s[4]]:255}:7!==o&&9!==o||(n={r:St[s[1]]<<4|St[s[2]],g:St[s[3]]<<4|St[s[4]],b:St[s[5]]<<4|St[s[6]],a:9===o?St[s[7]]<<4|St[s[8]]:255})),i=n||jt(t)||Gt(t)),this._rgb=i,this._valid=!!i}get valid(){return this._valid}get rgb(){var t=qt(this._rgb);return t&&(t.a=wt(t.a)),t}set rgb(t){this._rgb=Kt(t)}rgbString(){return this._valid?(t=this._rgb)&&(t.a<255?`rgba(${t.r}, ${t.g}, ${t.b}, ${wt(t.a)})`:`rgb(${t.r}, ${t.g}, ${t.b})`):void 0;var t}hexString(){return this._valid?At(this._rgb):void 0}hslString(){return this._valid?function(t){if(!t)return;const e=It(t),i=e[0],s=kt(e[1]),n=kt(e[2]);return t.a<255?`hsla(${i}, ${s}%, ${n}%, ${wt(t.a)})`:`hsl(${i}, ${s}%, ${n}%)`}(this._rgb):void 0}mix(t,e){if(t){const i=this.rgb,s=t.rgb;let n;const o=e===n?.5:e,a=2*o-1,r=i.a-s.a,l=((a*r==-1?a:(a+r)/(1+a*r))+1)/2;n=1-l,i.r=255&l*i.r+n*s.r+.5,i.g=255&l*i.g+n*s.g+.5,i.b=255&l*i.b+n*s.b+.5,i.a=o*i.a+(1-o)*s.a,this.rgb=i}return this}interpolate(t,e){return t&&(this._rgb=function(t,e,i){const s=Ut(wt(t.r)),n=Ut(wt(t.g)),o=Ut(wt(t.b));return{r:Mt(Yt(s+i*(Ut(wt(e.r))-s))),g:Mt(Yt(n+i*(Ut(wt(e.g))-n))),b:Mt(Yt(o+i*(Ut(wt(e.b))-o))),a:t.a+i*(e.a-t.a)}}(this._rgb,t._rgb,e)),this}clone(){return new Jt(this.rgb)}alpha(t){return this._rgb.a=Mt(t),this}clearer(t){return this._rgb.a*=1-t,this}greyscale(){const t=this._rgb,e=_t(.3*t.r+.59*t.g+.11*t.b);return t.r=t.g=t.b=e,this}opaquer(t){return this._rgb.a*=1+t,this}negate(){const t=this._rgb;return t.r=255-t.r,t.g=255-t.g,t.b=255-t.b,this}lighten(t){return Xt(this._rgb,2,t),this}darken(t){return Xt(this._rgb,2,-t),this}saturate(t){return Xt(this._rgb,1,t),this}desaturate(t){return Xt(this._rgb,1,-t),this}rotate(t){return function(t,e){var i=It(t);i[0]=Vt(i[0]+e),i=Ft(i),t.r=i[0],t.g=i[1],t.b=i[2]}(this._rgb,t),this}}function Zt(t){if(t&&"object"==typeof t){const e=t.toString();return"[object CanvasPattern]"===e||"[object CanvasGradient]"===e}return!1}function Qt(t){return Zt(t)?t:new Jt(t)}function te(t){return Zt(t)?t:new Jt(t).saturate(.5).darken(.1).hexString()}const ee=["x","y","borderWidth","radius","tension"],ie=["color","borderColor","backgroundColor"];const se=new Map;function ne(t,e,i){return function(t,e){e=e||{};const i=t+JSON.stringify(e);let s=se.get(i);return s||(s=new Intl.NumberFormat(t,e),se.set(i,s)),s}(e,i).format(t)}const oe={values:t=>n(t)?t:""+t,numeric(t,e,i){if(0===t)return"0";const s=this.chart.options.locale;let n,o=t;if(i.length>1){const e=Math.max(Math.abs(i[0].value),Math.abs(i[i.length-1].value));(e<1e-4||e>1e15)&&(n="scientific"),o=function(t,e){let i=e.length>3?e[2].value-e[1].value:e[1].value-e[0].value;Math.abs(i)>=1&&t!==Math.floor(t)&&(i=t-Math.floor(t));return i}(t,i)}const a=z(Math.abs(o)),r=isNaN(a)?1:Math.max(Math.min(-1*Math.floor(a),20),0),l={notation:n,minimumFractionDigits:r,maximumFractionDigits:r};return Object.assign(l,this.options.ticks.format),ne(t,s,l)},logarithmic(t,e,i){if(0===t)return"0";const s=i[e].significand||t/Math.pow(10,Math.floor(z(t)));return[1,2,3,5,10,15].includes(s)||e>.8*i.length?oe.numeric.call(this,t,e,i):""}};var ae={formatters:oe};const re=Object.create(null),le=Object.create(null);function he(t,e){if(!e)return t;const i=e.split(".");for(let e=0,s=i.length;et.chart.platform.getDevicePixelRatio(),this.elements={},this.events=["mousemove","mouseout","click","touchstart","touchmove"],this.font={family:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",size:12,style:"normal",lineHeight:1.2,weight:null},this.hover={},this.hoverBackgroundColor=(t,e)=>te(e.backgroundColor),this.hoverBorderColor=(t,e)=>te(e.borderColor),this.hoverColor=(t,e)=>te(e.color),this.indexAxis="x",this.interaction={mode:"nearest",intersect:!0,includeInvisible:!1},this.maintainAspectRatio=!0,this.onHover=null,this.onClick=null,this.parsing=!0,this.plugins={},this.responsive=!0,this.scale=void 0,this.scales={},this.showLine=!0,this.drawActiveElementsOnTop=!0,this.describe(t),this.apply(e)}set(t,e){return ce(this,t,e)}get(t){return he(this,t)}describe(t,e){return ce(le,t,e)}override(t,e){return ce(re,t,e)}route(t,e,i,s){const n=he(this,t),a=he(this,i),r="_"+e;Object.defineProperties(n,{[r]:{value:n[e],writable:!0},[e]:{enumerable:!0,get(){const t=this[r],e=a[s];return o(t)?Object.assign({},e,t):l(t,e)},set(t){this[r]=t}}})}apply(t){t.forEach((t=>t(this)))}}var ue=new de({_scriptable:t=>!t.startsWith("on"),_indexable:t=>"events"!==t,hover:{_fallback:"interaction"},interaction:{_scriptable:!1,_indexable:!1}},[function(t){t.set("animation",{delay:void 0,duration:1e3,easing:"easeOutQuart",fn:void 0,from:void 0,loop:void 0,to:void 0,type:void 0}),t.describe("animation",{_fallback:!1,_indexable:!1,_scriptable:t=>"onProgress"!==t&&"onComplete"!==t&&"fn"!==t}),t.set("animations",{colors:{type:"color",properties:ie},numbers:{type:"number",properties:ee}}),t.describe("animations",{_fallback:"animation"}),t.set("transitions",{active:{animation:{duration:400}},resize:{animation:{duration:0}},show:{animations:{colors:{from:"transparent"},visible:{type:"boolean",duration:0}}},hide:{animations:{colors:{to:"transparent"},visible:{type:"boolean",easing:"linear",fn:t=>0|t}}}})},function(t){t.set("layout",{autoPadding:!0,padding:{top:0,right:0,bottom:0,left:0}})},function(t){t.set("scale",{display:!0,offset:!1,reverse:!1,beginAtZero:!1,bounds:"ticks",clip:!0,grace:0,grid:{display:!0,lineWidth:1,drawOnChartArea:!0,drawTicks:!0,tickLength:8,tickWidth:(t,e)=>e.lineWidth,tickColor:(t,e)=>e.color,offset:!1},border:{display:!0,dash:[],dashOffset:0,width:1},title:{display:!1,text:"",padding:{top:4,bottom:4}},ticks:{minRotation:0,maxRotation:50,mirror:!1,textStrokeWidth:0,textStrokeColor:"",padding:3,display:!0,autoSkip:!0,autoSkipPadding:3,labelOffset:0,callback:ae.formatters.values,minor:{},major:{},align:"center",crossAlign:"near",showLabelBackdrop:!1,backdropColor:"rgba(255, 255, 255, 0.75)",backdropPadding:2}}),t.route("scale.ticks","color","","color"),t.route("scale.grid","color","","borderColor"),t.route("scale.border","color","","borderColor"),t.route("scale.title","color","","color"),t.describe("scale",{_fallback:!1,_scriptable:t=>!t.startsWith("before")&&!t.startsWith("after")&&"callback"!==t&&"parser"!==t,_indexable:t=>"borderDash"!==t&&"tickBorderDash"!==t&&"dash"!==t}),t.describe("scales",{_fallback:"scale"}),t.describe("scale.ticks",{_scriptable:t=>"backdropPadding"!==t&&"callback"!==t,_indexable:t=>"backdropPadding"!==t})}]);function fe(){return"undefined"!=typeof window&&"undefined"!=typeof document}function ge(t){let e=t.parentNode;return e&&"[object ShadowRoot]"===e.toString()&&(e=e.host),e}function pe(t,e,i){let s;return"string"==typeof t?(s=parseInt(t,10),-1!==t.indexOf("%")&&(s=s/100*e.parentNode[i])):s=t,s}const me=t=>t.ownerDocument.defaultView.getComputedStyle(t,null);function xe(t,e){return me(t).getPropertyValue(e)}const be=["top","right","bottom","left"];function _e(t,e,i){const s={};i=i?"-"+i:"";for(let n=0;n<4;n++){const o=be[n];s[o]=parseFloat(t[e+"-"+o+i])||0}return s.width=s.left+s.right,s.height=s.top+s.bottom,s}const ye=(t,e,i)=>(t>0||e>0)&&(!i||!i.shadowRoot);function ve(t,e){if("native"in t)return t;const{canvas:i,currentDevicePixelRatio:s}=e,n=me(i),o="border-box"===n.boxSizing,a=_e(n,"padding"),r=_e(n,"border","width"),{x:l,y:h,box:c}=function(t,e){const i=t.touches,s=i&&i.length?i[0]:t,{offsetX:n,offsetY:o}=s;let a,r,l=!1;if(ye(n,o,t.target))a=n,r=o;else{const t=e.getBoundingClientRect();a=s.clientX-t.left,r=s.clientY-t.top,l=!0}return{x:a,y:r,box:l}}(t,i),d=a.left+(c&&r.left),u=a.top+(c&&r.top);let{width:f,height:g}=e;return o&&(f-=a.width+r.width,g-=a.height+r.height),{x:Math.round((l-d)/f*i.width/s),y:Math.round((h-u)/g*i.height/s)}}const Me=t=>Math.round(10*t)/10;function we(t,e,i,s){const n=me(t),o=_e(n,"margin"),a=pe(n.maxWidth,t,"clientWidth")||T,r=pe(n.maxHeight,t,"clientHeight")||T,l=function(t,e,i){let s,n;if(void 0===e||void 0===i){const o=t&&ge(t);if(o){const t=o.getBoundingClientRect(),a=me(o),r=_e(a,"border","width"),l=_e(a,"padding");e=t.width-l.width-r.width,i=t.height-l.height-r.height,s=pe(a.maxWidth,o,"clientWidth"),n=pe(a.maxHeight,o,"clientHeight")}else e=t.clientWidth,i=t.clientHeight}return{width:e,height:i,maxWidth:s||T,maxHeight:n||T}}(t,e,i);let{width:h,height:c}=l;if("content-box"===n.boxSizing){const t=_e(n,"border","width"),e=_e(n,"padding");h-=e.width+t.width,c-=e.height+t.height}h=Math.max(0,h-o.width),c=Math.max(0,s?h/s:c-o.height),h=Me(Math.min(h,a,l.maxWidth)),c=Me(Math.min(c,r,l.maxHeight)),h&&!c&&(c=Me(h/2));return(void 0!==e||void 0!==i)&&s&&l.height&&c>l.height&&(c=l.height,h=Me(Math.floor(c*s))),{width:h,height:c}}function ke(t,e,i){const s=e||1,n=Math.floor(t.height*s),o=Math.floor(t.width*s);t.height=Math.floor(t.height),t.width=Math.floor(t.width);const a=t.canvas;return a.style&&(i||!a.style.height&&!a.style.width)&&(a.style.height=`${t.height}px`,a.style.width=`${t.width}px`),(t.currentDevicePixelRatio!==s||a.height!==n||a.width!==o)&&(t.currentDevicePixelRatio=s,a.height=n,a.width=o,t.ctx.setTransform(s,0,0,s,0,0),!0)}const Se=function(){let t=!1;try{const e={get passive(){return t=!0,!1}};fe()&&(window.addEventListener("test",null,e),window.removeEventListener("test",null,e))}catch(t){}return t}();function Pe(t,e){const i=xe(t,e),s=i&&i.match(/^(\d+)(\.\d+)?px$/);return s?+s[1]:void 0}function De(t){return!t||s(t.size)||s(t.family)?null:(t.style?t.style+" ":"")+(t.weight?t.weight+" ":"")+t.size+"px "+t.family}function Ce(t,e,i,s,n){let o=e[n];return o||(o=e[n]=t.measureText(n).width,i.push(n)),o>s&&(s=o),s}function Oe(t,e,i,s){let o=(s=s||{}).data=s.data||{},a=s.garbageCollect=s.garbageCollect||[];s.font!==e&&(o=s.data={},a=s.garbageCollect=[],s.font=e),t.save(),t.font=e;let r=0;const l=i.length;let h,c,d,u,f;for(h=0;hi.length){for(h=0;h0&&t.stroke()}}function Re(t,e,i){return i=i||.5,!e||t&&t.x>e.left-i&&t.xe.top-i&&t.y0&&""!==r.strokeColor;let c,d;for(t.save(),t.font=a.string,function(t,e){e.translation&&t.translate(e.translation[0],e.translation[1]),s(e.rotation)||t.rotate(e.rotation),e.color&&(t.fillStyle=e.color),e.textAlign&&(t.textAlign=e.textAlign),e.textBaseline&&(t.textBaseline=e.textBaseline)}(t,r),c=0;ct[0])){const o=i||t;void 0===s&&(s=ti("_fallback",t));const a={[Symbol.toStringTag]:"Object",_cacheable:!0,_scopes:t,_rootScopes:o,_fallback:s,_getTarget:n,override:i=>je([i,...t],e,o,s)};return new Proxy(a,{deleteProperty:(e,i)=>(delete e[i],delete e._keys,delete t[0][i],!0),get:(i,s)=>qe(i,s,(()=>function(t,e,i,s){let n;for(const o of e)if(n=ti(Ue(o,t),i),void 0!==n)return Xe(t,n)?Ze(i,s,t,n):n}(s,e,t,i))),getOwnPropertyDescriptor:(t,e)=>Reflect.getOwnPropertyDescriptor(t._scopes[0],e),getPrototypeOf:()=>Reflect.getPrototypeOf(t[0]),has:(t,e)=>ei(t).includes(e),ownKeys:t=>ei(t),set(t,e,i){const s=t._storage||(t._storage=n());return t[e]=s[e]=i,delete t._keys,!0}})}function $e(t,e,i,s){const a={_cacheable:!1,_proxy:t,_context:e,_subProxy:i,_stack:new Set,_descriptors:Ye(t,s),setContext:e=>$e(t,e,i,s),override:n=>$e(t.override(n),e,i,s)};return new Proxy(a,{deleteProperty:(e,i)=>(delete e[i],delete t[i],!0),get:(t,e,i)=>qe(t,e,(()=>function(t,e,i){const{_proxy:s,_context:a,_subProxy:r,_descriptors:l}=t;let h=s[e];S(h)&&l.isScriptable(e)&&(h=function(t,e,i,s){const{_proxy:n,_context:o,_subProxy:a,_stack:r}=i;if(r.has(t))throw new Error("Recursion detected: "+Array.from(r).join("->")+"->"+t);r.add(t);let l=e(o,a||s);r.delete(t),Xe(t,l)&&(l=Ze(n._scopes,n,t,l));return l}(e,h,t,i));n(h)&&h.length&&(h=function(t,e,i,s){const{_proxy:n,_context:a,_subProxy:r,_descriptors:l}=i;if(void 0!==a.index&&s(t))return e[a.index%e.length];if(o(e[0])){const i=e,s=n._scopes.filter((t=>t!==i));e=[];for(const o of i){const i=Ze(s,n,t,o);e.push($e(i,a,r&&r[t],l))}}return e}(e,h,t,l.isIndexable));Xe(e,h)&&(h=$e(h,a,r&&r[e],l));return h}(t,e,i))),getOwnPropertyDescriptor:(e,i)=>e._descriptors.allKeys?Reflect.has(t,i)?{enumerable:!0,configurable:!0}:void 0:Reflect.getOwnPropertyDescriptor(t,i),getPrototypeOf:()=>Reflect.getPrototypeOf(t),has:(e,i)=>Reflect.has(t,i),ownKeys:()=>Reflect.ownKeys(t),set:(e,i,s)=>(t[i]=s,delete e[i],!0)})}function Ye(t,e={scriptable:!0,indexable:!0}){const{_scriptable:i=e.scriptable,_indexable:s=e.indexable,_allKeys:n=e.allKeys}=t;return{allKeys:n,scriptable:i,indexable:s,isScriptable:S(i)?i:()=>i,isIndexable:S(s)?s:()=>s}}const Ue=(t,e)=>t?t+w(e):e,Xe=(t,e)=>o(e)&&"adapters"!==t&&(null===Object.getPrototypeOf(e)||e.constructor===Object);function qe(t,e,i){if(Object.prototype.hasOwnProperty.call(t,e)||"constructor"===e)return t[e];const s=i();return t[e]=s,s}function Ke(t,e,i){return S(t)?t(e,i):t}const Ge=(t,e)=>!0===t?e:"string"==typeof t?M(e,t):void 0;function Je(t,e,i,s,n){for(const o of e){const e=Ge(i,o);if(e){t.add(e);const o=Ke(e._fallback,i,n);if(void 0!==o&&o!==i&&o!==s)return o}else if(!1===e&&void 0!==s&&i!==s)return null}return!1}function Ze(t,e,i,s){const a=e._rootScopes,r=Ke(e._fallback,i,s),l=[...t,...a],h=new Set;h.add(s);let c=Qe(h,l,i,r||i,s);return null!==c&&((void 0===r||r===i||(c=Qe(h,l,r,c,s),null!==c))&&je(Array.from(h),[""],a,r,(()=>function(t,e,i){const s=t._getTarget();e in s||(s[e]={});const a=s[e];if(n(a)&&o(i))return i;return a||{}}(e,i,s))))}function Qe(t,e,i,s,n){for(;i;)i=Je(t,e,i,s,n);return i}function ti(t,e){for(const i of e){if(!i)continue;const e=i[t];if(void 0!==e)return e}}function ei(t){let e=t._keys;return e||(e=t._keys=function(t){const e=new Set;for(const i of t)for(const t of Object.keys(i).filter((t=>!t.startsWith("_"))))e.add(t);return Array.from(e)}(t._scopes)),e}function ii(t,e,i,s){const{iScale:n}=t,{key:o="r"}=this._parsing,a=new Array(s);let r,l,h,c;for(r=0,l=s;re"x"===t?"y":"x";function ai(t,e,i,s){const n=t.skip?e:t,o=e,a=i.skip?e:i,r=q(o,n),l=q(a,o);let h=r/(r+l),c=l/(r+l);h=isNaN(h)?0:h,c=isNaN(c)?0:c;const d=s*h,u=s*c;return{previous:{x:o.x-d*(a.x-n.x),y:o.y-d*(a.y-n.y)},next:{x:o.x+u*(a.x-n.x),y:o.y+u*(a.y-n.y)}}}function ri(t,e="x"){const i=oi(e),s=t.length,n=Array(s).fill(0),o=Array(s);let a,r,l,h=ni(t,0);for(a=0;a!t.skip))),"monotone"===e.cubicInterpolationMode)ri(t,n);else{let i=s?t[t.length-1]:t[0];for(o=0,a=t.length;o0===t||1===t,di=(t,e,i)=>-Math.pow(2,10*(t-=1))*Math.sin((t-e)*O/i),ui=(t,e,i)=>Math.pow(2,-10*t)*Math.sin((t-e)*O/i)+1,fi={linear:t=>t,easeInQuad:t=>t*t,easeOutQuad:t=>-t*(t-2),easeInOutQuad:t=>(t/=.5)<1?.5*t*t:-.5*(--t*(t-2)-1),easeInCubic:t=>t*t*t,easeOutCubic:t=>(t-=1)*t*t+1,easeInOutCubic:t=>(t/=.5)<1?.5*t*t*t:.5*((t-=2)*t*t+2),easeInQuart:t=>t*t*t*t,easeOutQuart:t=>-((t-=1)*t*t*t-1),easeInOutQuart:t=>(t/=.5)<1?.5*t*t*t*t:-.5*((t-=2)*t*t*t-2),easeInQuint:t=>t*t*t*t*t,easeOutQuint:t=>(t-=1)*t*t*t*t+1,easeInOutQuint:t=>(t/=.5)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2),easeInSine:t=>1-Math.cos(t*E),easeOutSine:t=>Math.sin(t*E),easeInOutSine:t=>-.5*(Math.cos(C*t)-1),easeInExpo:t=>0===t?0:Math.pow(2,10*(t-1)),easeOutExpo:t=>1===t?1:1-Math.pow(2,-10*t),easeInOutExpo:t=>ci(t)?t:t<.5?.5*Math.pow(2,10*(2*t-1)):.5*(2-Math.pow(2,-10*(2*t-1))),easeInCirc:t=>t>=1?t:-(Math.sqrt(1-t*t)-1),easeOutCirc:t=>Math.sqrt(1-(t-=1)*t),easeInOutCirc:t=>(t/=.5)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1),easeInElastic:t=>ci(t)?t:di(t,.075,.3),easeOutElastic:t=>ci(t)?t:ui(t,.075,.3),easeInOutElastic(t){const e=.1125;return ci(t)?t:t<.5?.5*di(2*t,e,.45):.5+.5*ui(2*t-1,e,.45)},easeInBack(t){const e=1.70158;return t*t*((e+1)*t-e)},easeOutBack(t){const e=1.70158;return(t-=1)*t*((e+1)*t+e)+1},easeInOutBack(t){let e=1.70158;return(t/=.5)<1?t*t*((1+(e*=1.525))*t-e)*.5:.5*((t-=2)*t*((1+(e*=1.525))*t+e)+2)},easeInBounce:t=>1-fi.easeOutBounce(1-t),easeOutBounce(t){const e=7.5625,i=2.75;return t<1/i?e*t*t:t<2/i?e*(t-=1.5/i)*t+.75:t<2.5/i?e*(t-=2.25/i)*t+.9375:e*(t-=2.625/i)*t+.984375},easeInOutBounce:t=>t<.5?.5*fi.easeInBounce(2*t):.5*fi.easeOutBounce(2*t-1)+.5};function gi(t,e,i,s){return{x:t.x+i*(e.x-t.x),y:t.y+i*(e.y-t.y)}}function pi(t,e,i,s){return{x:t.x+i*(e.x-t.x),y:"middle"===s?i<.5?t.y:e.y:"after"===s?i<1?t.y:e.y:i>0?e.y:t.y}}function mi(t,e,i,s){const n={x:t.cp2x,y:t.cp2y},o={x:e.cp1x,y:e.cp1y},a=gi(t,n,i),r=gi(n,o,i),l=gi(o,e,i),h=gi(a,r,i),c=gi(r,l,i);return gi(h,c,i)}const xi=/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/,bi=/^(normal|italic|initial|inherit|unset|(oblique( -?[0-9]?[0-9]deg)?))$/;function _i(t,e){const i=(""+t).match(xi);if(!i||"normal"===i[1])return 1.2*e;switch(t=+i[2],i[3]){case"px":return t;case"%":t/=100}return e*t}const yi=t=>+t||0;function vi(t,e){const i={},s=o(e),n=s?Object.keys(e):e,a=o(t)?s?i=>l(t[i],t[e[i]]):e=>t[e]:()=>t;for(const t of n)i[t]=yi(a(t));return i}function Mi(t){return vi(t,{top:"y",right:"x",bottom:"y",left:"x"})}function wi(t){return vi(t,["topLeft","topRight","bottomLeft","bottomRight"])}function ki(t){const e=Mi(t);return e.width=e.left+e.right,e.height=e.top+e.bottom,e}function Si(t,e){t=t||{},e=e||ue.font;let i=l(t.size,e.size);"string"==typeof i&&(i=parseInt(i,10));let s=l(t.style,e.style);s&&!(""+s).match(bi)&&(console.warn('Invalid font style specified: "'+s+'"'),s=void 0);const n={family:l(t.family,e.family),lineHeight:_i(l(t.lineHeight,e.lineHeight),i),size:i,style:s,weight:l(t.weight,e.weight),string:""};return n.string=De(n),n}function Pi(t,e,i,s){let o,a,r,l=!0;for(o=0,a=t.length;oi&&0===t?0:t+e;return{min:a(s,-Math.abs(o)),max:a(n,o)}}function Ci(t,e){return Object.assign(Object.create(t),e)}function Oi(t,e,i){return t?function(t,e){return{x:i=>t+t+e-i,setWidth(t){e=t},textAlign:t=>"center"===t?t:"right"===t?"left":"right",xPlus:(t,e)=>t-e,leftForLtr:(t,e)=>t-e}}(e,i):{x:t=>t,setWidth(t){},textAlign:t=>t,xPlus:(t,e)=>t+e,leftForLtr:(t,e)=>t}}function Ai(t,e){let i,s;"ltr"!==e&&"rtl"!==e||(i=t.canvas.style,s=[i.getPropertyValue("direction"),i.getPropertyPriority("direction")],i.setProperty("direction",e,"important"),t.prevTextDirection=s)}function Ti(t,e){void 0!==e&&(delete t.prevTextDirection,t.canvas.style.setProperty("direction",e[0],e[1]))}function Li(t){return"angle"===t?{between:J,compare:K,normalize:G}:{between:tt,compare:(t,e)=>t-e,normalize:t=>t}}function Ei({start:t,end:e,count:i,loop:s,style:n}){return{start:t%i,end:e%i,loop:s&&(e-t+1)%i==0,style:n}}function Ri(t,e,i){if(!i)return[t];const{property:s,start:n,end:o}=i,a=e.length,{compare:r,between:l,normalize:h}=Li(s),{start:c,end:d,loop:u,style:f}=function(t,e,i){const{property:s,start:n,end:o}=i,{between:a,normalize:r}=Li(s),l=e.length;let h,c,{start:d,end:u,loop:f}=t;if(f){for(d+=l,u+=l,h=0,c=l;hb||l(n,x,p)&&0!==r(n,x),v=()=>!b||0===r(o,p)||l(o,x,p);for(let t=c,i=c;t<=d;++t)m=e[t%a],m.skip||(p=h(m[s]),p!==x&&(b=l(p,n,o),null===_&&y()&&(_=0===r(p,n)?t:i),null!==_&&v()&&(g.push(Ei({start:_,end:t,loop:u,count:a,style:f})),_=null),i=t,x=p));return null!==_&&g.push(Ei({start:_,end:d,loop:u,count:a,style:f})),g}function Ii(t,e){const i=[],s=t.segments;for(let n=0;nn&&t[o%e].skip;)o--;return o%=e,{start:n,end:o}}(i,n,o,s);if(!0===s)return Fi(t,[{start:a,end:r,loop:o}],i,e);return Fi(t,function(t,e,i,s){const n=t.length,o=[];let a,r=e,l=t[e];for(a=e+1;a<=i;++a){const i=t[a%n];i.skip||i.stop?l.skip||(s=!1,o.push({start:e%n,end:(a-1)%n,loop:s}),e=r=i.stop?a:null):(r=a,l.skip&&(e=a)),l=i}return null!==r&&o.push({start:e%n,end:r%n,loop:s}),o}(i,a,r!s(t[e.axis])));n.lo-=Math.max(0,a);const r=i.slice(n.hi).findIndex((t=>!s(t[e.axis])));n.hi+=Math.max(0,r)}return n}if(o._sharedOptions){const t=a[0],s="function"==typeof t.getRange&&t.getRange(e);if(s){const t=r(a,e,i-s),n=r(a,e,i+s);return{lo:t.lo,hi:n.hi}}}}return{lo:0,hi:a.length-1}}function $i(t,e,i,s,n){const o=t.getSortedVisibleDatasetMetas(),a=i[e];for(let t=0,i=o.length;t{t[a]&&t[a](e[i],n)&&(o.push({element:t,datasetIndex:s,index:l}),r=r||t.inRange(e.x,e.y,n))})),s&&!r?[]:o}var Ki={evaluateInteractionItems:$i,modes:{index(t,e,i,s){const n=ve(e,t),o=i.axis||"x",a=i.includeInvisible||!1,r=i.intersect?Yi(t,n,o,s,a):Xi(t,n,o,!1,s,a),l=[];return r.length?(t.getSortedVisibleDatasetMetas().forEach((t=>{const e=r[0].index,i=t.data[e];i&&!i.skip&&l.push({element:i,datasetIndex:t.index,index:e})})),l):[]},dataset(t,e,i,s){const n=ve(e,t),o=i.axis||"xy",a=i.includeInvisible||!1;let r=i.intersect?Yi(t,n,o,s,a):Xi(t,n,o,!1,s,a);if(r.length>0){const e=r[0].datasetIndex,i=t.getDatasetMeta(e).data;r=[];for(let t=0;tYi(t,ve(e,t),i.axis||"xy",s,i.includeInvisible||!1),nearest(t,e,i,s){const n=ve(e,t),o=i.axis||"xy",a=i.includeInvisible||!1;return Xi(t,n,o,i.intersect,s,a)},x:(t,e,i,s)=>qi(t,ve(e,t),"x",i.intersect,s),y:(t,e,i,s)=>qi(t,ve(e,t),"y",i.intersect,s)}};const Gi=["left","top","right","bottom"];function Ji(t,e){return t.filter((t=>t.pos===e))}function Zi(t,e){return t.filter((t=>-1===Gi.indexOf(t.pos)&&t.box.axis===e))}function Qi(t,e){return t.sort(((t,i)=>{const s=e?i:t,n=e?t:i;return s.weight===n.weight?s.index-n.index:s.weight-n.weight}))}function ts(t,e){const i=function(t){const e={};for(const i of t){const{stack:t,pos:s,stackWeight:n}=i;if(!t||!Gi.includes(s))continue;const o=e[t]||(e[t]={count:0,placed:0,weight:0,size:0});o.count++,o.weight+=n}return e}(t),{vBoxMaxWidth:s,hBoxMaxHeight:n}=e;let o,a,r;for(o=0,a=t.length;o{s[t]=Math.max(e[t],i[t])})),s}return s(t?["left","right"]:["top","bottom"])}function os(t,e,i,s){const n=[];let o,a,r,l,h,c;for(o=0,a=t.length,h=0;ot.box.fullSize)),!0),s=Qi(Ji(e,"left"),!0),n=Qi(Ji(e,"right")),o=Qi(Ji(e,"top"),!0),a=Qi(Ji(e,"bottom")),r=Zi(e,"x"),l=Zi(e,"y");return{fullSize:i,leftAndTop:s.concat(o),rightAndBottom:n.concat(l).concat(a).concat(r),chartArea:Ji(e,"chartArea"),vertical:s.concat(n).concat(l),horizontal:o.concat(a).concat(r)}}(t.boxes),l=r.vertical,h=r.horizontal;u(t.boxes,(t=>{"function"==typeof t.beforeLayout&&t.beforeLayout()}));const c=l.reduce(((t,e)=>e.box.options&&!1===e.box.options.display?t:t+1),0)||1,d=Object.freeze({outerWidth:e,outerHeight:i,padding:n,availableWidth:o,availableHeight:a,vBoxMaxWidth:o/2/c,hBoxMaxHeight:a/2}),f=Object.assign({},n);is(f,ki(s));const g=Object.assign({maxPadding:f,w:o,h:a,x:n.left,y:n.top},n),p=ts(l.concat(h),d);os(r.fullSize,g,d,p),os(l,g,d,p),os(h,g,d,p)&&os(l,g,d,p),function(t){const e=t.maxPadding;function i(i){const s=Math.max(e[i]-t[i],0);return t[i]+=s,s}t.y+=i("top"),t.x+=i("left"),i("right"),i("bottom")}(g),rs(r.leftAndTop,g,d,p),g.x+=g.w,g.y+=g.h,rs(r.rightAndBottom,g,d,p),t.chartArea={left:g.left,top:g.top,right:g.left+g.w,bottom:g.top+g.h,height:g.h,width:g.w},u(r.chartArea,(e=>{const i=e.box;Object.assign(i,t.chartArea),i.update(g.w,g.h,{left:0,top:0,right:0,bottom:0})}))}};class hs{acquireContext(t,e){}releaseContext(t){return!1}addEventListener(t,e,i){}removeEventListener(t,e,i){}getDevicePixelRatio(){return 1}getMaximumSize(t,e,i,s){return e=Math.max(0,e||t.width),i=i||t.height,{width:e,height:Math.max(0,s?Math.floor(e/s):i)}}isAttached(t){return!0}updateConfig(t){}}class cs extends hs{acquireContext(t){return t&&t.getContext&&t.getContext("2d")||null}updateConfig(t){t.options.animation=!1}}const ds="$chartjs",us={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"},fs=t=>null===t||""===t;const gs=!!Se&&{passive:!0};function ps(t,e,i){t&&t.canvas&&t.canvas.removeEventListener(e,i,gs)}function ms(t,e){for(const i of t)if(i===e||i.contains(e))return!0}function xs(t,e,i){const s=t.canvas,n=new MutationObserver((t=>{let e=!1;for(const i of t)e=e||ms(i.addedNodes,s),e=e&&!ms(i.removedNodes,s);e&&i()}));return n.observe(document,{childList:!0,subtree:!0}),n}function bs(t,e,i){const s=t.canvas,n=new MutationObserver((t=>{let e=!1;for(const i of t)e=e||ms(i.removedNodes,s),e=e&&!ms(i.addedNodes,s);e&&i()}));return n.observe(document,{childList:!0,subtree:!0}),n}const _s=new Map;let ys=0;function vs(){const t=window.devicePixelRatio;t!==ys&&(ys=t,_s.forEach(((e,i)=>{i.currentDevicePixelRatio!==t&&e()})))}function Ms(t,e,i){const s=t.canvas,n=s&&ge(s);if(!n)return;const o=ct(((t,e)=>{const s=n.clientWidth;i(t,e),s{const e=t[0],i=e.contentRect.width,s=e.contentRect.height;0===i&&0===s||o(i,s)}));return a.observe(n),function(t,e){_s.size||window.addEventListener("resize",vs),_s.set(t,e)}(t,o),a}function ws(t,e,i){i&&i.disconnect(),"resize"===e&&function(t){_s.delete(t),_s.size||window.removeEventListener("resize",vs)}(t)}function ks(t,e,i){const s=t.canvas,n=ct((e=>{null!==t.ctx&&i(function(t,e){const i=us[t.type]||t.type,{x:s,y:n}=ve(t,e);return{type:i,chart:e,native:t,x:void 0!==s?s:null,y:void 0!==n?n:null}}(e,t))}),t);return function(t,e,i){t&&t.addEventListener(e,i,gs)}(s,e,n),n}class Ss extends hs{acquireContext(t,e){const i=t&&t.getContext&&t.getContext("2d");return i&&i.canvas===t?(function(t,e){const i=t.style,s=t.getAttribute("height"),n=t.getAttribute("width");if(t[ds]={initial:{height:s,width:n,style:{display:i.display,height:i.height,width:i.width}}},i.display=i.display||"block",i.boxSizing=i.boxSizing||"border-box",fs(n)){const e=Pe(t,"width");void 0!==e&&(t.width=e)}if(fs(s))if(""===t.style.height)t.height=t.width/(e||2);else{const e=Pe(t,"height");void 0!==e&&(t.height=e)}}(t,e),i):null}releaseContext(t){const e=t.canvas;if(!e[ds])return!1;const i=e[ds].initial;["height","width"].forEach((t=>{const n=i[t];s(n)?e.removeAttribute(t):e.setAttribute(t,n)}));const n=i.style||{};return Object.keys(n).forEach((t=>{e.style[t]=n[t]})),e.width=e.width,delete e[ds],!0}addEventListener(t,e,i){this.removeEventListener(t,e);const s=t.$proxies||(t.$proxies={}),n={attach:xs,detach:bs,resize:Ms}[e]||ks;s[e]=n(t,e,i)}removeEventListener(t,e){const i=t.$proxies||(t.$proxies={}),s=i[e];if(!s)return;({attach:ws,detach:ws,resize:ws}[e]||ps)(t,e,s),i[e]=void 0}getDevicePixelRatio(){return window.devicePixelRatio}getMaximumSize(t,e,i,s){return we(t,e,i,s)}isAttached(t){const e=t&&ge(t);return!(!e||!e.isConnected)}}function Ps(t){return!fe()||"undefined"!=typeof OffscreenCanvas&&t instanceof OffscreenCanvas?cs:Ss}var Ds=Object.freeze({__proto__:null,BasePlatform:hs,BasicPlatform:cs,DomPlatform:Ss,_detectPlatform:Ps});const Cs="transparent",Os={boolean:(t,e,i)=>i>.5?e:t,color(t,e,i){const s=Qt(t||Cs),n=s.valid&&Qt(e||Cs);return n&&n.valid?n.mix(s,i).hexString():e},number:(t,e,i)=>t+(e-t)*i};class As{constructor(t,e,i,s){const n=e[i];s=Pi([t.to,s,n,t.from]);const o=Pi([t.from,n,s]);this._active=!0,this._fn=t.fn||Os[t.type||typeof o],this._easing=fi[t.easing]||fi.linear,this._start=Math.floor(Date.now()+(t.delay||0)),this._duration=this._total=Math.floor(t.duration),this._loop=!!t.loop,this._target=e,this._prop=i,this._from=o,this._to=s,this._promises=void 0}active(){return this._active}update(t,e,i){if(this._active){this._notify(!1);const s=this._target[this._prop],n=i-this._start,o=this._duration-n;this._start=i,this._duration=Math.floor(Math.max(o,t.duration)),this._total+=n,this._loop=!!t.loop,this._to=Pi([t.to,e,s,t.from]),this._from=Pi([t.from,s,e])}}cancel(){this._active&&(this.tick(Date.now()),this._active=!1,this._notify(!1))}tick(t){const e=t-this._start,i=this._duration,s=this._prop,n=this._from,o=this._loop,a=this._to;let r;if(this._active=n!==a&&(o||e1?2-r:r,r=this._easing(Math.min(1,Math.max(0,r))),this._target[s]=this._fn(n,a,r))}wait(){const t=this._promises||(this._promises=[]);return new Promise(((e,i)=>{t.push({res:e,rej:i})}))}_notify(t){const e=t?"res":"rej",i=this._promises||[];for(let t=0;t{const a=t[s];if(!o(a))return;const r={};for(const t of e)r[t]=a[t];(n(a.properties)&&a.properties||[s]).forEach((t=>{t!==s&&i.has(t)||i.set(t,r)}))}))}_animateOptions(t,e){const i=e.options,s=function(t,e){if(!e)return;let i=t.options;if(!i)return void(t.options=e);i.$shared&&(t.options=i=Object.assign({},i,{$shared:!1,$animations:{}}));return i}(t,i);if(!s)return[];const n=this._createAnimations(s,i);return i.$shared&&function(t,e){const i=[],s=Object.keys(e);for(let e=0;e{t.options=i}),(()=>{})),n}_createAnimations(t,e){const i=this._properties,s=[],n=t.$animations||(t.$animations={}),o=Object.keys(e),a=Date.now();let r;for(r=o.length-1;r>=0;--r){const l=o[r];if("$"===l.charAt(0))continue;if("options"===l){s.push(...this._animateOptions(t,e));continue}const h=e[l];let c=n[l];const d=i.get(l);if(c){if(d&&c.active()){c.update(d,h,a);continue}c.cancel()}d&&d.duration?(n[l]=c=new As(d,t,l,h),s.push(c)):t[l]=h}return s}update(t,e){if(0===this._properties.size)return void Object.assign(t,e);const i=this._createAnimations(t,e);return i.length?(bt.add(this._chart,i),!0):void 0}}function Ls(t,e){const i=t&&t.options||{},s=i.reverse,n=void 0===i.min?e:0,o=void 0===i.max?e:0;return{start:s?o:n,end:s?n:o}}function Es(t,e){const i=[],s=t._getSortedDatasetMetas(e);let n,o;for(n=0,o=s.length;n0||!i&&e<0)return n.index}return null}function Vs(t,e){const{chart:i,_cachedMeta:s}=t,n=i._stacks||(i._stacks={}),{iScale:o,vScale:a,index:r}=s,l=o.axis,h=a.axis,c=function(t,e,i){return`${t.id}.${e.id}.${i.stack||i.type}`}(o,a,s),d=e.length;let u;for(let t=0;ti[t].axis===e)).shift()}function Ws(t,e){const i=t.controller.index,s=t.vScale&&t.vScale.axis;if(s){e=e||t._parsed;for(const t of e){const e=t._stacks;if(!e||void 0===e[s]||void 0===e[s][i])return;delete e[s][i],void 0!==e[s]._visualValues&&void 0!==e[s]._visualValues[i]&&delete e[s]._visualValues[i]}}}const Ns=t=>"reset"===t||"none"===t,Hs=(t,e)=>e?t:Object.assign({},t);class js{static defaults={};static datasetElementType=null;static dataElementType=null;constructor(t,e){this.chart=t,this._ctx=t.ctx,this.index=e,this._cachedDataOpts={},this._cachedMeta=this.getMeta(),this._type=this._cachedMeta.type,this.options=void 0,this._parsing=!1,this._data=void 0,this._objectData=void 0,this._sharedOptions=void 0,this._drawStart=void 0,this._drawCount=void 0,this.enableOptionSharing=!1,this.supportsDecimation=!1,this.$context=void 0,this._syncList=[],this.datasetElementType=new.target.datasetElementType,this.dataElementType=new.target.dataElementType,this.initialize()}initialize(){const t=this._cachedMeta;this.configure(),this.linkScales(),t._stacked=Is(t.vScale,t),this.addElements(),this.options.fill&&!this.chart.isPluginEnabled("filler")&&console.warn("Tried to use the 'fill' option without the 'Filler' plugin enabled. Please import and register the 'Filler' plugin and make sure it is not disabled in the options")}updateIndex(t){this.index!==t&&Ws(this._cachedMeta),this.index=t}linkScales(){const t=this.chart,e=this._cachedMeta,i=this.getDataset(),s=(t,e,i,s)=>"x"===t?e:"r"===t?s:i,n=e.xAxisID=l(i.xAxisID,Bs(t,"x")),o=e.yAxisID=l(i.yAxisID,Bs(t,"y")),a=e.rAxisID=l(i.rAxisID,Bs(t,"r")),r=e.indexAxis,h=e.iAxisID=s(r,n,o,a),c=e.vAxisID=s(r,o,n,a);e.xScale=this.getScaleForId(n),e.yScale=this.getScaleForId(o),e.rScale=this.getScaleForId(a),e.iScale=this.getScaleForId(h),e.vScale=this.getScaleForId(c)}getDataset(){return this.chart.data.datasets[this.index]}getMeta(){return this.chart.getDatasetMeta(this.index)}getScaleForId(t){return this.chart.scales[t]}_getOtherScale(t){const e=this._cachedMeta;return t===e.iScale?e.vScale:e.iScale}reset(){this._update("reset")}_destroy(){const t=this._cachedMeta;this._data&&rt(this._data,this),t._stacked&&Ws(t)}_dataCheck(){const t=this.getDataset(),e=t.data||(t.data=[]),i=this._data;if(o(e)){const t=this._cachedMeta;this._data=function(t,e){const{iScale:i,vScale:s}=e,n="x"===i.axis?"x":"y",o="x"===s.axis?"x":"y",a=Object.keys(t),r=new Array(a.length);let l,h,c;for(l=0,h=a.length;l0&&i._parsed[t-1];if(!1===this._parsing)i._parsed=s,i._sorted=!0,d=s;else{d=n(s[t])?this.parseArrayData(i,s,t,e):o(s[t])?this.parseObjectData(i,s,t,e):this.parsePrimitiveData(i,s,t,e);const a=()=>null===c[l]||f&&c[l]t&&!e.hidden&&e._stacked&&{keys:Es(i,!0),values:null})(e,i,this.chart),h={min:Number.POSITIVE_INFINITY,max:Number.NEGATIVE_INFINITY},{min:c,max:d}=function(t){const{min:e,max:i,minDefined:s,maxDefined:n}=t.getUserBounds();return{min:s?e:Number.NEGATIVE_INFINITY,max:n?i:Number.POSITIVE_INFINITY}}(r);let u,f;function g(){f=s[u];const e=f[r.axis];return!a(f[t.axis])||c>e||d=0;--u)if(!g()){this.updateRangeFromParsed(h,t,f,l);break}return h}getAllParsedValues(t){const e=this._cachedMeta._parsed,i=[];let s,n,o;for(s=0,n=e.length;s=0&&tthis.getContext(i,s,e)),c);return f.$shared&&(f.$shared=r,n[o]=Object.freeze(Hs(f,r))),f}_resolveAnimations(t,e,i){const s=this.chart,n=this._cachedDataOpts,o=`animation-${e}`,a=n[o];if(a)return a;let r;if(!1!==s.options.animation){const s=this.chart.config,n=s.datasetAnimationScopeKeys(this._type,e),o=s.getOptionScopes(this.getDataset(),n);r=s.createResolver(o,this.getContext(t,i,e))}const l=new Ts(s,r&&r.animations);return r&&r._cacheable&&(n[o]=Object.freeze(l)),l}getSharedOptions(t){if(t.$shared)return this._sharedOptions||(this._sharedOptions=Object.assign({},t))}includeOptions(t,e){return!e||Ns(t)||this.chart._animationsDisabled}_getSharedOptions(t,e){const i=this.resolveDataElementOptions(t,e),s=this._sharedOptions,n=this.getSharedOptions(i),o=this.includeOptions(e,n)||n!==s;return this.updateSharedOptions(n,e,i),{sharedOptions:n,includeOptions:o}}updateElement(t,e,i,s){Ns(s)?Object.assign(t,i):this._resolveAnimations(e,s).update(t,i)}updateSharedOptions(t,e,i){t&&!Ns(e)&&this._resolveAnimations(void 0,e).update(t,i)}_setStyle(t,e,i,s){t.active=s;const n=this.getStyle(e,s);this._resolveAnimations(e,i,s).update(t,{options:!s&&this.getSharedOptions(n)||n})}removeHoverStyle(t,e,i){this._setStyle(t,i,"active",!1)}setHoverStyle(t,e,i){this._setStyle(t,i,"active",!0)}_removeDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!1)}_setDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!0)}_resyncElements(t){const e=this._data,i=this._cachedMeta.data;for(const[t,e,i]of this._syncList)this[t](e,i);this._syncList=[];const s=i.length,n=e.length,o=Math.min(n,s);o&&this.parse(0,o),n>s?this._insertElements(s,n-s,t):n{for(t.length+=e,a=t.length-1;a>=o;a--)t[a]=t[a-e]};for(r(n),a=t;a{s[t]=i[t]&&i[t].active()?i[t]._to:this[t]})),s}}function Ys(t,e){const i=t.options.ticks,n=function(t){const e=t.options.offset,i=t._tickSize(),s=t._length/i+(e?0:1),n=t._maxLength/i;return Math.floor(Math.min(s,n))}(t),o=Math.min(i.maxTicksLimit||n,n),a=i.major.enabled?function(t){const e=[];let i,s;for(i=0,s=t.length;io)return function(t,e,i,s){let n,o=0,a=i[0];for(s=Math.ceil(s),n=0;nn)return e}return Math.max(n,1)}(a,e,o);if(r>0){let t,i;const n=r>1?Math.round((h-l)/(r-1)):null;for(Us(e,c,d,s(n)?0:l-n,l),t=0,i=r-1;t"top"===e||"left"===e?t[e]+i:t[e]-i,qs=(t,e)=>Math.min(e||t,t);function Ks(t,e){const i=[],s=t.length/e,n=t.length;let o=0;for(;oa+r)))return h}function Js(t){return t.drawTicks?t.tickLength:0}function Zs(t,e){if(!t.display)return 0;const i=Si(t.font,e),s=ki(t.padding);return(n(t.text)?t.text.length:1)*i.lineHeight+s.height}function Qs(t,e,i){let s=ut(t);return(i&&"right"!==e||!i&&"right"===e)&&(s=(t=>"left"===t?"right":"right"===t?"left":t)(s)),s}class tn extends $s{constructor(t){super(),this.id=t.id,this.type=t.type,this.options=void 0,this.ctx=t.ctx,this.chart=t.chart,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.width=void 0,this.height=void 0,this._margins={left:0,right:0,top:0,bottom:0},this.maxWidth=void 0,this.maxHeight=void 0,this.paddingTop=void 0,this.paddingBottom=void 0,this.paddingLeft=void 0,this.paddingRight=void 0,this.axis=void 0,this.labelRotation=void 0,this.min=void 0,this.max=void 0,this._range=void 0,this.ticks=[],this._gridLineItems=null,this._labelItems=null,this._labelSizes=null,this._length=0,this._maxLength=0,this._longestTextCache={},this._startPixel=void 0,this._endPixel=void 0,this._reversePixels=!1,this._userMax=void 0,this._userMin=void 0,this._suggestedMax=void 0,this._suggestedMin=void 0,this._ticksLength=0,this._borderValue=0,this._cache={},this._dataLimitsCached=!1,this.$context=void 0}init(t){this.options=t.setContext(this.getContext()),this.axis=t.axis,this._userMin=this.parse(t.min),this._userMax=this.parse(t.max),this._suggestedMin=this.parse(t.suggestedMin),this._suggestedMax=this.parse(t.suggestedMax)}parse(t,e){return t}getUserBounds(){let{_userMin:t,_userMax:e,_suggestedMin:i,_suggestedMax:s}=this;return t=r(t,Number.POSITIVE_INFINITY),e=r(e,Number.NEGATIVE_INFINITY),i=r(i,Number.POSITIVE_INFINITY),s=r(s,Number.NEGATIVE_INFINITY),{min:r(t,i),max:r(e,s),minDefined:a(t),maxDefined:a(e)}}getMinMax(t){let e,{min:i,max:s,minDefined:n,maxDefined:o}=this.getUserBounds();if(n&&o)return{min:i,max:s};const a=this.getMatchingVisibleMetas();for(let r=0,l=a.length;rs?s:i,s=n&&i>s?i:s,{min:r(i,r(s,i)),max:r(s,r(i,s))}}getPadding(){return{left:this.paddingLeft||0,top:this.paddingTop||0,right:this.paddingRight||0,bottom:this.paddingBottom||0}}getTicks(){return this.ticks}getLabels(){const t=this.chart.data;return this.options.labels||(this.isHorizontal()?t.xLabels:t.yLabels)||t.labels||[]}getLabelItems(t=this.chart.chartArea){return this._labelItems||(this._labelItems=this._computeLabelItems(t))}beforeLayout(){this._cache={},this._dataLimitsCached=!1}beforeUpdate(){d(this.options.beforeUpdate,[this])}update(t,e,i){const{beginAtZero:s,grace:n,ticks:o}=this.options,a=o.sampleSize;this.beforeUpdate(),this.maxWidth=t,this.maxHeight=e,this._margins=i=Object.assign({left:0,right:0,top:0,bottom:0},i),this.ticks=null,this._labelSizes=null,this._gridLineItems=null,this._labelItems=null,this.beforeSetDimensions(),this.setDimensions(),this.afterSetDimensions(),this._maxLength=this.isHorizontal()?this.width+i.left+i.right:this.height+i.top+i.bottom,this._dataLimitsCached||(this.beforeDataLimits(),this.determineDataLimits(),this.afterDataLimits(),this._range=Di(this,n,s),this._dataLimitsCached=!0),this.beforeBuildTicks(),this.ticks=this.buildTicks()||[],this.afterBuildTicks();const r=a=n||i<=1||!this.isHorizontal())return void(this.labelRotation=s);const h=this._getLabelSizes(),c=h.widest.width,d=h.highest.height,u=Z(this.chart.width-c,0,this.maxWidth);o=t.offset?this.maxWidth/i:u/(i-1),c+6>o&&(o=u/(i-(t.offset?.5:1)),a=this.maxHeight-Js(t.grid)-e.padding-Zs(t.title,this.chart.options.font),r=Math.sqrt(c*c+d*d),l=Y(Math.min(Math.asin(Z((h.highest.height+6)/o,-1,1)),Math.asin(Z(a/r,-1,1))-Math.asin(Z(d/r,-1,1)))),l=Math.max(s,Math.min(n,l))),this.labelRotation=l}afterCalculateLabelRotation(){d(this.options.afterCalculateLabelRotation,[this])}afterAutoSkip(){}beforeFit(){d(this.options.beforeFit,[this])}fit(){const t={width:0,height:0},{chart:e,options:{ticks:i,title:s,grid:n}}=this,o=this._isVisible(),a=this.isHorizontal();if(o){const o=Zs(s,e.options.font);if(a?(t.width=this.maxWidth,t.height=Js(n)+o):(t.height=this.maxHeight,t.width=Js(n)+o),i.display&&this.ticks.length){const{first:e,last:s,widest:n,highest:o}=this._getLabelSizes(),r=2*i.padding,l=$(this.labelRotation),h=Math.cos(l),c=Math.sin(l);if(a){const e=i.mirror?0:c*n.width+h*o.height;t.height=Math.min(this.maxHeight,t.height+e+r)}else{const e=i.mirror?0:h*n.width+c*o.height;t.width=Math.min(this.maxWidth,t.width+e+r)}this._calculatePadding(e,s,c,h)}}this._handleMargins(),a?(this.width=this._length=e.width-this._margins.left-this._margins.right,this.height=t.height):(this.width=t.width,this.height=this._length=e.height-this._margins.top-this._margins.bottom)}_calculatePadding(t,e,i,s){const{ticks:{align:n,padding:o},position:a}=this.options,r=0!==this.labelRotation,l="top"!==a&&"x"===this.axis;if(this.isHorizontal()){const a=this.getPixelForTick(0)-this.left,h=this.right-this.getPixelForTick(this.ticks.length-1);let c=0,d=0;r?l?(c=s*t.width,d=i*e.height):(c=i*t.height,d=s*e.width):"start"===n?d=e.width:"end"===n?c=t.width:"inner"!==n&&(c=t.width/2,d=e.width/2),this.paddingLeft=Math.max((c-a+o)*this.width/(this.width-a),0),this.paddingRight=Math.max((d-h+o)*this.width/(this.width-h),0)}else{let i=e.height/2,s=t.height/2;"start"===n?(i=0,s=t.height):"end"===n&&(i=e.height,s=0),this.paddingTop=i+o,this.paddingBottom=s+o}}_handleMargins(){this._margins&&(this._margins.left=Math.max(this.paddingLeft,this._margins.left),this._margins.top=Math.max(this.paddingTop,this._margins.top),this._margins.right=Math.max(this.paddingRight,this._margins.right),this._margins.bottom=Math.max(this.paddingBottom,this._margins.bottom))}afterFit(){d(this.options.afterFit,[this])}isHorizontal(){const{axis:t,position:e}=this.options;return"top"===e||"bottom"===e||"x"===t}isFullSize(){return this.options.fullSize}_convertTicksToLabels(t){let e,i;for(this.beforeTickToLabelConversion(),this.generateTickLabels(t),e=0,i=t.length;e{const i=t.gc,s=i.length/2;let n;if(s>e){for(n=0;n({width:r[t]||0,height:l[t]||0});return{first:P(0),last:P(e-1),widest:P(k),highest:P(S),widths:r,heights:l}}getLabelForValue(t){return t}getPixelForValue(t,e){return NaN}getValueForPixel(t){}getPixelForTick(t){const e=this.ticks;return t<0||t>e.length-1?null:this.getPixelForValue(e[t].value)}getPixelForDecimal(t){this._reversePixels&&(t=1-t);const e=this._startPixel+t*this._length;return Q(this._alignToPixels?Ae(this.chart,e,0):e)}getDecimalForPixel(t){const e=(t-this._startPixel)/this._length;return this._reversePixels?1-e:e}getBasePixel(){return this.getPixelForValue(this.getBaseValue())}getBaseValue(){const{min:t,max:e}=this;return t<0&&e<0?e:t>0&&e>0?t:0}getContext(t){const e=this.ticks||[];if(t>=0&&ta*s?a/i:r/s:r*s0}_computeGridLineItems(t){const e=this.axis,i=this.chart,s=this.options,{grid:n,position:a,border:r}=s,h=n.offset,c=this.isHorizontal(),d=this.ticks.length+(h?1:0),u=Js(n),f=[],g=r.setContext(this.getContext()),p=g.display?g.width:0,m=p/2,x=function(t){return Ae(i,t,p)};let b,_,y,v,M,w,k,S,P,D,C,O;if("top"===a)b=x(this.bottom),w=this.bottom-u,S=b-m,D=x(t.top)+m,O=t.bottom;else if("bottom"===a)b=x(this.top),D=t.top,O=x(t.bottom)-m,w=b+m,S=this.top+u;else if("left"===a)b=x(this.right),M=this.right-u,k=b-m,P=x(t.left)+m,C=t.right;else if("right"===a)b=x(this.left),P=t.left,C=x(t.right)-m,M=b+m,k=this.left+u;else if("x"===e){if("center"===a)b=x((t.top+t.bottom)/2+.5);else if(o(a)){const t=Object.keys(a)[0],e=a[t];b=x(this.chart.scales[t].getPixelForValue(e))}D=t.top,O=t.bottom,w=b+m,S=w+u}else if("y"===e){if("center"===a)b=x((t.left+t.right)/2);else if(o(a)){const t=Object.keys(a)[0],e=a[t];b=x(this.chart.scales[t].getPixelForValue(e))}M=b-m,k=M-u,P=t.left,C=t.right}const A=l(s.ticks.maxTicksLimit,d),T=Math.max(1,Math.ceil(d/A));for(_=0;_0&&(o-=s/2)}d={left:o,top:n,width:s+e.width,height:i+e.height,color:t.backdropColor}}x.push({label:v,font:P,textOffset:O,options:{rotation:m,color:i,strokeColor:o,strokeWidth:h,textAlign:f,textBaseline:A,translation:[M,w],backdrop:d}})}return x}_getXAxisLabelAlignment(){const{position:t,ticks:e}=this.options;if(-$(this.labelRotation))return"top"===t?"left":"right";let i="center";return"start"===e.align?i="left":"end"===e.align?i="right":"inner"===e.align&&(i="inner"),i}_getYAxisLabelAlignment(t){const{position:e,ticks:{crossAlign:i,mirror:s,padding:n}}=this.options,o=t+n,a=this._getLabelSizes().widest.width;let r,l;return"left"===e?s?(l=this.right+n,"near"===i?r="left":"center"===i?(r="center",l+=a/2):(r="right",l+=a)):(l=this.right-o,"near"===i?r="right":"center"===i?(r="center",l-=a/2):(r="left",l=this.left)):"right"===e?s?(l=this.left+n,"near"===i?r="right":"center"===i?(r="center",l-=a/2):(r="left",l-=a)):(l=this.left+o,"near"===i?r="left":"center"===i?(r="center",l+=a/2):(r="right",l=this.right)):r="right",{textAlign:r,x:l}}_computeLabelArea(){if(this.options.ticks.mirror)return;const t=this.chart,e=this.options.position;return"left"===e||"right"===e?{top:0,left:this.left,bottom:t.height,right:this.right}:"top"===e||"bottom"===e?{top:this.top,left:0,bottom:this.bottom,right:t.width}:void 0}drawBackground(){const{ctx:t,options:{backgroundColor:e},left:i,top:s,width:n,height:o}=this;e&&(t.save(),t.fillStyle=e,t.fillRect(i,s,n,o),t.restore())}getLineWidthForValue(t){const e=this.options.grid;if(!this._isVisible()||!e.display)return 0;const i=this.ticks.findIndex((e=>e.value===t));if(i>=0){return e.setContext(this.getContext(i)).lineWidth}return 0}drawGrid(t){const e=this.options.grid,i=this.ctx,s=this._gridLineItems||(this._gridLineItems=this._computeGridLineItems(t));let n,o;const a=(t,e,s)=>{s.width&&s.color&&(i.save(),i.lineWidth=s.width,i.strokeStyle=s.color,i.setLineDash(s.borderDash||[]),i.lineDashOffset=s.borderDashOffset,i.beginPath(),i.moveTo(t.x,t.y),i.lineTo(e.x,e.y),i.stroke(),i.restore())};if(e.display)for(n=0,o=s.length;n{this.drawBackground(),this.drawGrid(t),this.drawTitle()}},{z:s,draw:()=>{this.drawBorder()}},{z:e,draw:t=>{this.drawLabels(t)}}]:[{z:e,draw:t=>{this.draw(t)}}]}getMatchingVisibleMetas(t){const e=this.chart.getSortedVisibleDatasetMetas(),i=this.axis+"AxisID",s=[];let n,o;for(n=0,o=e.length;n{const s=i.split("."),n=s.pop(),o=[t].concat(s).join("."),a=e[i].split("."),r=a.pop(),l=a.join(".");ue.route(o,n,l,r)}))}(e,t.defaultRoutes);t.descriptors&&ue.describe(e,t.descriptors)}(t,o,i),this.override&&ue.override(t.id,t.overrides)),o}get(t){return this.items[t]}unregister(t){const e=this.items,i=t.id,s=this.scope;i in e&&delete e[i],s&&i in ue[s]&&(delete ue[s][i],this.override&&delete re[i])}}class sn{constructor(){this.controllers=new en(js,"datasets",!0),this.elements=new en($s,"elements"),this.plugins=new en(Object,"plugins"),this.scales=new en(tn,"scales"),this._typedRegistries=[this.controllers,this.scales,this.elements]}add(...t){this._each("register",t)}remove(...t){this._each("unregister",t)}addControllers(...t){this._each("register",t,this.controllers)}addElements(...t){this._each("register",t,this.elements)}addPlugins(...t){this._each("register",t,this.plugins)}addScales(...t){this._each("register",t,this.scales)}getController(t){return this._get(t,this.controllers,"controller")}getElement(t){return this._get(t,this.elements,"element")}getPlugin(t){return this._get(t,this.plugins,"plugin")}getScale(t){return this._get(t,this.scales,"scale")}removeControllers(...t){this._each("unregister",t,this.controllers)}removeElements(...t){this._each("unregister",t,this.elements)}removePlugins(...t){this._each("unregister",t,this.plugins)}removeScales(...t){this._each("unregister",t,this.scales)}_each(t,e,i){[...e].forEach((e=>{const s=i||this._getRegistryForType(e);i||s.isForType(e)||s===this.plugins&&e.id?this._exec(t,s,e):u(e,(e=>{const s=i||this._getRegistryForType(e);this._exec(t,s,e)}))}))}_exec(t,e,i){const s=w(t);d(i["before"+s],[],i),e[t](i),d(i["after"+s],[],i)}_getRegistryForType(t){for(let e=0;et.filter((t=>!e.some((e=>t.plugin.id===e.plugin.id))));this._notify(s(e,i),t,"stop"),this._notify(s(i,e),t,"start")}}function an(t,e){return e||!1!==t?!0===t?{}:t:null}function rn(t,{plugin:e,local:i},s,n){const o=t.pluginScopeKeys(e),a=t.getOptionScopes(s,o);return i&&e.defaults&&a.push(e.defaults),t.createResolver(a,n,[""],{scriptable:!1,indexable:!1,allKeys:!0})}function ln(t,e){const i=ue.datasets[t]||{};return((e.datasets||{})[t]||{}).indexAxis||e.indexAxis||i.indexAxis||"x"}function hn(t){if("x"===t||"y"===t||"r"===t)return t}function cn(t,...e){if(hn(t))return t;for(const s of e){const e=s.axis||("top"===(i=s.position)||"bottom"===i?"x":"left"===i||"right"===i?"y":void 0)||t.length>1&&hn(t[0].toLowerCase());if(e)return e}var i;throw new Error(`Cannot determine type of '${t}' axis. Please provide 'axis' or 'position' option.`)}function dn(t,e,i){if(i[e+"AxisID"]===t)return{axis:e}}function un(t,e){const i=re[t.type]||{scales:{}},s=e.scales||{},n=ln(t.type,e),a=Object.create(null);return Object.keys(s).forEach((e=>{const r=s[e];if(!o(r))return console.error(`Invalid scale configuration for scale: ${e}`);if(r._proxy)return console.warn(`Ignoring resolver passed as options for scale: ${e}`);const l=cn(e,r,function(t,e){if(e.data&&e.data.datasets){const i=e.data.datasets.filter((e=>e.xAxisID===t||e.yAxisID===t));if(i.length)return dn(t,"x",i[0])||dn(t,"y",i[0])}return{}}(e,t),ue.scales[r.type]),h=function(t,e){return t===e?"_index_":"_value_"}(l,n),c=i.scales||{};a[e]=b(Object.create(null),[{axis:l},r,c[l],c[h]])})),t.data.datasets.forEach((i=>{const n=i.type||t.type,o=i.indexAxis||ln(n,e),r=(re[n]||{}).scales||{};Object.keys(r).forEach((t=>{const e=function(t,e){let i=t;return"_index_"===t?i=e:"_value_"===t&&(i="x"===e?"y":"x"),i}(t,o),n=i[e+"AxisID"]||e;a[n]=a[n]||Object.create(null),b(a[n],[{axis:e},s[n],r[t]])}))})),Object.keys(a).forEach((t=>{const e=a[t];b(e,[ue.scales[e.type],ue.scale])})),a}function fn(t){const e=t.options||(t.options={});e.plugins=l(e.plugins,{}),e.scales=un(t,e)}function gn(t){return(t=t||{}).datasets=t.datasets||[],t.labels=t.labels||[],t}const pn=new Map,mn=new Set;function xn(t,e){let i=pn.get(t);return i||(i=e(),pn.set(t,i),mn.add(i)),i}const bn=(t,e,i)=>{const s=M(e,i);void 0!==s&&t.add(s)};class _n{constructor(t){this._config=function(t){return(t=t||{}).data=gn(t.data),fn(t),t}(t),this._scopeCache=new Map,this._resolverCache=new Map}get platform(){return this._config.platform}get type(){return this._config.type}set type(t){this._config.type=t}get data(){return this._config.data}set data(t){this._config.data=gn(t)}get options(){return this._config.options}set options(t){this._config.options=t}get plugins(){return this._config.plugins}update(){const t=this._config;this.clearCache(),fn(t)}clearCache(){this._scopeCache.clear(),this._resolverCache.clear()}datasetScopeKeys(t){return xn(t,(()=>[[`datasets.${t}`,""]]))}datasetAnimationScopeKeys(t,e){return xn(`${t}.transition.${e}`,(()=>[[`datasets.${t}.transitions.${e}`,`transitions.${e}`],[`datasets.${t}`,""]]))}datasetElementScopeKeys(t,e){return xn(`${t}-${e}`,(()=>[[`datasets.${t}.elements.${e}`,`datasets.${t}`,`elements.${e}`,""]]))}pluginScopeKeys(t){const e=t.id;return xn(`${this.type}-plugin-${e}`,(()=>[[`plugins.${e}`,...t.additionalOptionScopes||[]]]))}_cachedScopes(t,e){const i=this._scopeCache;let s=i.get(t);return s&&!e||(s=new Map,i.set(t,s)),s}getOptionScopes(t,e,i){const{options:s,type:n}=this,o=this._cachedScopes(t,i),a=o.get(e);if(a)return a;const r=new Set;e.forEach((e=>{t&&(r.add(t),e.forEach((e=>bn(r,t,e)))),e.forEach((t=>bn(r,s,t))),e.forEach((t=>bn(r,re[n]||{},t))),e.forEach((t=>bn(r,ue,t))),e.forEach((t=>bn(r,le,t)))}));const l=Array.from(r);return 0===l.length&&l.push(Object.create(null)),mn.has(e)&&o.set(e,l),l}chartOptionScopes(){const{options:t,type:e}=this;return[t,re[e]||{},ue.datasets[e]||{},{type:e},ue,le]}resolveNamedOptions(t,e,i,s=[""]){const o={$shared:!0},{resolver:a,subPrefixes:r}=yn(this._resolverCache,t,s);let l=a;if(function(t,e){const{isScriptable:i,isIndexable:s}=Ye(t);for(const o of e){const e=i(o),a=s(o),r=(a||e)&&t[o];if(e&&(S(r)||vn(r))||a&&n(r))return!0}return!1}(a,e)){o.$shared=!1;l=$e(a,i=S(i)?i():i,this.createResolver(t,i,r))}for(const t of e)o[t]=l[t];return o}createResolver(t,e,i=[""],s){const{resolver:n}=yn(this._resolverCache,t,i);return o(e)?$e(n,e,void 0,s):n}}function yn(t,e,i){let s=t.get(e);s||(s=new Map,t.set(e,s));const n=i.join();let o=s.get(n);if(!o){o={resolver:je(e,i),subPrefixes:i.filter((t=>!t.toLowerCase().includes("hover")))},s.set(n,o)}return o}const vn=t=>o(t)&&Object.getOwnPropertyNames(t).some((e=>S(t[e])));const Mn=["top","bottom","left","right","chartArea"];function wn(t,e){return"top"===t||"bottom"===t||-1===Mn.indexOf(t)&&"x"===e}function kn(t,e){return function(i,s){return i[t]===s[t]?i[e]-s[e]:i[t]-s[t]}}function Sn(t){const e=t.chart,i=e.options.animation;e.notifyPlugins("afterRender"),d(i&&i.onComplete,[t],e)}function Pn(t){const e=t.chart,i=e.options.animation;d(i&&i.onProgress,[t],e)}function Dn(t){return fe()&&"string"==typeof t?t=document.getElementById(t):t&&t.length&&(t=t[0]),t&&t.canvas&&(t=t.canvas),t}const Cn={},On=t=>{const e=Dn(t);return Object.values(Cn).filter((t=>t.canvas===e)).pop()};function An(t,e,i){const s=Object.keys(t);for(const n of s){const s=+n;if(s>=e){const o=t[n];delete t[n],(i>0||s>e)&&(t[s+i]=o)}}}class Tn{static defaults=ue;static instances=Cn;static overrides=re;static registry=nn;static version="4.5.0";static getChart=On;static register(...t){nn.add(...t),Ln()}static unregister(...t){nn.remove(...t),Ln()}constructor(t,e){const s=this.config=new _n(e),n=Dn(t),o=On(n);if(o)throw new Error("Canvas is already in use. Chart with ID '"+o.id+"' must be destroyed before the canvas with ID '"+o.canvas.id+"' can be reused.");const a=s.createResolver(s.chartOptionScopes(),this.getContext());this.platform=new(s.platform||Ps(n)),this.platform.updateConfig(s);const r=this.platform.acquireContext(n,a.aspectRatio),l=r&&r.canvas,h=l&&l.height,c=l&&l.width;this.id=i(),this.ctx=r,this.canvas=l,this.width=c,this.height=h,this._options=a,this._aspectRatio=this.aspectRatio,this._layers=[],this._metasets=[],this._stacks=void 0,this.boxes=[],this.currentDevicePixelRatio=void 0,this.chartArea=void 0,this._active=[],this._lastEvent=void 0,this._listeners={},this._responsiveListeners=void 0,this._sortedMetasets=[],this.scales={},this._plugins=new on,this.$proxies={},this._hiddenIndices={},this.attached=!1,this._animationsDisabled=void 0,this.$context=void 0,this._doResize=dt((t=>this.update(t)),a.resizeDelay||0),this._dataChanges=[],Cn[this.id]=this,r&&l?(bt.listen(this,"complete",Sn),bt.listen(this,"progress",Pn),this._initialize(),this.attached&&this.update()):console.error("Failed to create chart: can't acquire context from the given item")}get aspectRatio(){const{options:{aspectRatio:t,maintainAspectRatio:e},width:i,height:n,_aspectRatio:o}=this;return s(t)?e&&o?o:n?i/n:null:t}get data(){return this.config.data}set data(t){this.config.data=t}get options(){return this._options}set options(t){this.config.options=t}get registry(){return nn}_initialize(){return this.notifyPlugins("beforeInit"),this.options.responsive?this.resize():ke(this,this.options.devicePixelRatio),this.bindEvents(),this.notifyPlugins("afterInit"),this}clear(){return Te(this.canvas,this.ctx),this}stop(){return bt.stop(this),this}resize(t,e){bt.running(this)?this._resizeBeforeDraw={width:t,height:e}:this._resize(t,e)}_resize(t,e){const i=this.options,s=this.canvas,n=i.maintainAspectRatio&&this.aspectRatio,o=this.platform.getMaximumSize(s,t,e,n),a=i.devicePixelRatio||this.platform.getDevicePixelRatio(),r=this.width?"resize":"attach";this.width=o.width,this.height=o.height,this._aspectRatio=this.aspectRatio,ke(this,a,!0)&&(this.notifyPlugins("resize",{size:o}),d(i.onResize,[this,o],this),this.attached&&this._doResize(r)&&this.render())}ensureScalesHaveIDs(){u(this.options.scales||{},((t,e)=>{t.id=e}))}buildOrUpdateScales(){const t=this.options,e=t.scales,i=this.scales,s=Object.keys(i).reduce(((t,e)=>(t[e]=!1,t)),{});let n=[];e&&(n=n.concat(Object.keys(e).map((t=>{const i=e[t],s=cn(t,i),n="r"===s,o="x"===s;return{options:i,dposition:n?"chartArea":o?"bottom":"left",dtype:n?"radialLinear":o?"category":"linear"}})))),u(n,(e=>{const n=e.options,o=n.id,a=cn(o,n),r=l(n.type,e.dtype);void 0!==n.position&&wn(n.position,a)===wn(e.dposition)||(n.position=e.dposition),s[o]=!0;let h=null;if(o in i&&i[o].type===r)h=i[o];else{h=new(nn.getScale(r))({id:o,type:r,ctx:this.ctx,chart:this}),i[h.id]=h}h.init(n,t)})),u(s,((t,e)=>{t||delete i[e]})),u(i,(t=>{ls.configure(this,t,t.options),ls.addBox(this,t)}))}_updateMetasets(){const t=this._metasets,e=this.data.datasets.length,i=t.length;if(t.sort(((t,e)=>t.index-e.index)),i>e){for(let t=e;te.length&&delete this._stacks,t.forEach(((t,i)=>{0===e.filter((e=>e===t._dataset)).length&&this._destroyDatasetMeta(i)}))}buildOrUpdateControllers(){const t=[],e=this.data.datasets;let i,s;for(this._removeUnreferencedMetasets(),i=0,s=e.length;i{this.getDatasetMeta(e).controller.reset()}),this)}reset(){this._resetElements(),this.notifyPlugins("reset")}update(t){const e=this.config;e.update();const i=this._options=e.createResolver(e.chartOptionScopes(),this.getContext()),s=this._animationsDisabled=!i.animation;if(this._updateScales(),this._checkEventBindings(),this._updateHiddenIndices(),this._plugins.invalidate(),!1===this.notifyPlugins("beforeUpdate",{mode:t,cancelable:!0}))return;const n=this.buildOrUpdateControllers();this.notifyPlugins("beforeElementsUpdate");let o=0;for(let t=0,e=this.data.datasets.length;t{t.reset()})),this._updateDatasets(t),this.notifyPlugins("afterUpdate",{mode:t}),this._layers.sort(kn("z","_idx"));const{_active:a,_lastEvent:r}=this;r?this._eventHandler(r,!0):a.length&&this._updateHoverStyles(a,a,!0),this.render()}_updateScales(){u(this.scales,(t=>{ls.removeBox(this,t)})),this.ensureScalesHaveIDs(),this.buildOrUpdateScales()}_checkEventBindings(){const t=this.options,e=new Set(Object.keys(this._listeners)),i=new Set(t.events);P(e,i)&&!!this._responsiveListeners===t.responsive||(this.unbindEvents(),this.bindEvents())}_updateHiddenIndices(){const{_hiddenIndices:t}=this,e=this._getUniformDataChanges()||[];for(const{method:i,start:s,count:n}of e){An(t,s,"_removeElements"===i?-n:n)}}_getUniformDataChanges(){const t=this._dataChanges;if(!t||!t.length)return;this._dataChanges=[];const e=this.data.datasets.length,i=e=>new Set(t.filter((t=>t[0]===e)).map(((t,e)=>e+","+t.splice(1).join(",")))),s=i(0);for(let t=1;tt.split(","))).map((t=>({method:t[1],start:+t[2],count:+t[3]})))}_updateLayout(t){if(!1===this.notifyPlugins("beforeLayout",{cancelable:!0}))return;ls.update(this,this.width,this.height,t);const e=this.chartArea,i=e.width<=0||e.height<=0;this._layers=[],u(this.boxes,(t=>{i&&"chartArea"===t.position||(t.configure&&t.configure(),this._layers.push(...t._layers()))}),this),this._layers.forEach(((t,e)=>{t._idx=e})),this.notifyPlugins("afterLayout")}_updateDatasets(t){if(!1!==this.notifyPlugins("beforeDatasetsUpdate",{mode:t,cancelable:!0})){for(let t=0,e=this.data.datasets.length;t=0;--e)this._drawDataset(t[e]);this.notifyPlugins("afterDatasetsDraw")}_drawDataset(t){const e=this.ctx,i={meta:t,index:t.index,cancelable:!0},s=Ni(this,t);!1!==this.notifyPlugins("beforeDatasetDraw",i)&&(s&&Ie(e,s),t.controller.draw(),s&&ze(e),i.cancelable=!1,this.notifyPlugins("afterDatasetDraw",i))}isPointInArea(t){return Re(t,this.chartArea,this._minPadding)}getElementsAtEventForMode(t,e,i,s){const n=Ki.modes[e];return"function"==typeof n?n(this,t,i,s):[]}getDatasetMeta(t){const e=this.data.datasets[t],i=this._metasets;let s=i.filter((t=>t&&t._dataset===e)).pop();return s||(s={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null,order:e&&e.order||0,index:t,_dataset:e,_parsed:[],_sorted:!1},i.push(s)),s}getContext(){return this.$context||(this.$context=Ci(null,{chart:this,type:"chart"}))}getVisibleDatasetCount(){return this.getSortedVisibleDatasetMetas().length}isDatasetVisible(t){const e=this.data.datasets[t];if(!e)return!1;const i=this.getDatasetMeta(t);return"boolean"==typeof i.hidden?!i.hidden:!e.hidden}setDatasetVisibility(t,e){this.getDatasetMeta(t).hidden=!e}toggleDataVisibility(t){this._hiddenIndices[t]=!this._hiddenIndices[t]}getDataVisibility(t){return!this._hiddenIndices[t]}_updateVisibility(t,e,i){const s=i?"show":"hide",n=this.getDatasetMeta(t),o=n.controller._resolveAnimations(void 0,s);k(e)?(n.data[e].hidden=!i,this.update()):(this.setDatasetVisibility(t,i),o.update(n,{visible:i}),this.update((e=>e.datasetIndex===t?s:void 0)))}hide(t,e){this._updateVisibility(t,e,!1)}show(t,e){this._updateVisibility(t,e,!0)}_destroyDatasetMeta(t){const e=this._metasets[t];e&&e.controller&&e.controller._destroy(),delete this._metasets[t]}_stop(){let t,e;for(this.stop(),bt.remove(this),t=0,e=this.data.datasets.length;t{e.addEventListener(this,i,s),t[i]=s},s=(t,e,i)=>{t.offsetX=e,t.offsetY=i,this._eventHandler(t)};u(this.options.events,(t=>i(t,s)))}bindResponsiveEvents(){this._responsiveListeners||(this._responsiveListeners={});const t=this._responsiveListeners,e=this.platform,i=(i,s)=>{e.addEventListener(this,i,s),t[i]=s},s=(i,s)=>{t[i]&&(e.removeEventListener(this,i,s),delete t[i])},n=(t,e)=>{this.canvas&&this.resize(t,e)};let o;const a=()=>{s("attach",a),this.attached=!0,this.resize(),i("resize",n),i("detach",o)};o=()=>{this.attached=!1,s("resize",n),this._stop(),this._resize(0,0),i("attach",a)},e.isAttached(this.canvas)?a():o()}unbindEvents(){u(this._listeners,((t,e)=>{this.platform.removeEventListener(this,e,t)})),this._listeners={},u(this._responsiveListeners,((t,e)=>{this.platform.removeEventListener(this,e,t)})),this._responsiveListeners=void 0}updateHoverStyle(t,e,i){const s=i?"set":"remove";let n,o,a,r;for("dataset"===e&&(n=this.getDatasetMeta(t[0].datasetIndex),n.controller["_"+s+"DatasetHoverStyle"]()),a=0,r=t.length;a{const i=this.getDatasetMeta(t);if(!i)throw new Error("No dataset found at index "+t);return{datasetIndex:t,element:i.data[e],index:e}}));!f(i,e)&&(this._active=i,this._lastEvent=null,this._updateHoverStyles(i,e))}notifyPlugins(t,e,i){return this._plugins.notify(this,t,e,i)}isPluginEnabled(t){return 1===this._plugins._cache.filter((e=>e.plugin.id===t)).length}_updateHoverStyles(t,e,i){const s=this.options.hover,n=(t,e)=>t.filter((t=>!e.some((e=>t.datasetIndex===e.datasetIndex&&t.index===e.index)))),o=n(e,t),a=i?t:n(t,e);o.length&&this.updateHoverStyle(o,s.mode,!1),a.length&&s.mode&&this.updateHoverStyle(a,s.mode,!0)}_eventHandler(t,e){const i={event:t,replay:e,cancelable:!0,inChartArea:this.isPointInArea(t)},s=e=>(e.options.events||this.options.events).includes(t.native.type);if(!1===this.notifyPlugins("beforeEvent",i,s))return;const n=this._handleEvent(t,e,i.inChartArea);return i.cancelable=!1,this.notifyPlugins("afterEvent",i,s),(n||i.changed)&&this.render(),this}_handleEvent(t,e,i){const{_active:s=[],options:n}=this,o=e,a=this._getActiveElements(t,s,i,o),r=D(t),l=function(t,e,i,s){return i&&"mouseout"!==t.type?s?e:t:null}(t,this._lastEvent,i,r);i&&(this._lastEvent=null,d(n.onHover,[t,a,this],this),r&&d(n.onClick,[t,a,this],this));const h=!f(a,s);return(h||e)&&(this._active=a,this._updateHoverStyles(a,s,e)),this._lastEvent=l,h}_getActiveElements(t,e,i,s){if("mouseout"===t.type)return[];if(!i)return e;const n=this.options.hover;return this.getElementsAtEventForMode(t,n.mode,n,s)}}function Ln(){return u(Tn.instances,(t=>t._plugins.invalidate()))}function En(){throw new Error("This method is not implemented: Check that a complete date adapter is provided.")}class Rn{static override(t){Object.assign(Rn.prototype,t)}options;constructor(t){this.options=t||{}}init(){}formats(){return En()}parse(){return En()}format(){return En()}add(){return En()}diff(){return En()}startOf(){return En()}endOf(){return En()}}var In={_date:Rn};function zn(t){const e=t.iScale,i=function(t,e){if(!t._cache.$bar){const i=t.getMatchingVisibleMetas(e);let s=[];for(let e=0,n=i.length;et-e)))}return t._cache.$bar}(e,t.type);let s,n,o,a,r=e._length;const l=()=>{32767!==o&&-32768!==o&&(k(a)&&(r=Math.min(r,Math.abs(o-a)||r)),a=o)};for(s=0,n=i.length;sMath.abs(r)&&(l=r,h=a),e[i.axis]=h,e._custom={barStart:l,barEnd:h,start:n,end:o,min:a,max:r}}(t,e,i,s):e[i.axis]=i.parse(t,s),e}function Vn(t,e,i,s){const n=t.iScale,o=t.vScale,a=n.getLabels(),r=n===o,l=[];let h,c,d,u;for(h=i,c=i+s;ht.x,i="left",s="right"):(e=t.base"spacing"!==t,_indexable:t=>"spacing"!==t&&!t.startsWith("borderDash")&&!t.startsWith("hoverBorderDash")};static overrides={aspectRatio:1,plugins:{legend:{labels:{generateLabels(t){const e=t.data;if(e.labels.length&&e.datasets.length){const{labels:{pointStyle:i,color:s}}=t.legend.options;return e.labels.map(((e,n)=>{const o=t.getDatasetMeta(0).controller.getStyle(n);return{text:e,fillStyle:o.backgroundColor,strokeStyle:o.borderColor,fontColor:s,lineWidth:o.borderWidth,pointStyle:i,hidden:!t.getDataVisibility(n),index:n}}))}return[]}},onClick(t,e,i){i.chart.toggleDataVisibility(e.index),i.chart.update()}}}};constructor(t,e){super(t,e),this.enableOptionSharing=!0,this.innerRadius=void 0,this.outerRadius=void 0,this.offsetX=void 0,this.offsetY=void 0}linkScales(){}parse(t,e){const i=this.getDataset().data,s=this._cachedMeta;if(!1===this._parsing)s._parsed=i;else{let n,a,r=t=>+i[t];if(o(i[t])){const{key:t="value"}=this._parsing;r=e=>+M(i[e],t)}for(n=t,a=t+e;nJ(t,r,l,!0)?1:Math.max(e,e*i,s,s*i),g=(t,e,s)=>J(t,r,l,!0)?-1:Math.min(e,e*i,s,s*i),p=f(0,h,d),m=f(E,c,u),x=g(C,h,d),b=g(C+E,c,u);s=(p-x)/2,n=(m-b)/2,o=-(p+x)/2,a=-(m+b)/2}return{ratioX:s,ratioY:n,offsetX:o,offsetY:a}}(u,d,r),x=(i.width-o)/f,b=(i.height-o)/g,_=Math.max(Math.min(x,b)/2,0),y=c(this.options.radius,_),v=(y-Math.max(y*r,0))/this._getVisibleDatasetWeightTotal();this.offsetX=p*y,this.offsetY=m*y,s.total=this.calculateTotal(),this.outerRadius=y-v*this._getRingWeightOffset(this.index),this.innerRadius=Math.max(this.outerRadius-v*l,0),this.updateElements(n,0,n.length,t)}_circumference(t,e){const i=this.options,s=this._cachedMeta,n=this._getCircumference();return e&&i.animation.animateRotate||!this.chart.getDataVisibility(t)||null===s._parsed[t]||s.data[t].hidden?0:this.calculateCircumference(s._parsed[t]*n/O)}updateElements(t,e,i,s){const n="reset"===s,o=this.chart,a=o.chartArea,r=o.options.animation,l=(a.left+a.right)/2,h=(a.top+a.bottom)/2,c=n&&r.animateScale,d=c?0:this.innerRadius,u=c?0:this.outerRadius,{sharedOptions:f,includeOptions:g}=this._getSharedOptions(e,s);let p,m=this._getRotation();for(p=0;p0&&!isNaN(t)?O*(Math.abs(t)/e):0}getLabelAndValue(t){const e=this._cachedMeta,i=this.chart,s=i.data.labels||[],n=ne(e._parsed[t],i.options.locale);return{label:s[t]||"",value:n}}getMaxBorderWidth(t){let e=0;const i=this.chart;let s,n,o,a,r;if(!t)for(s=0,n=i.data.datasets.length;s{const o=t.getDatasetMeta(0).controller.getStyle(n);return{text:e,fillStyle:o.backgroundColor,strokeStyle:o.borderColor,fontColor:s,lineWidth:o.borderWidth,pointStyle:i,hidden:!t.getDataVisibility(n),index:n}}))}return[]}},onClick(t,e,i){i.chart.toggleDataVisibility(e.index),i.chart.update()}}},scales:{r:{type:"radialLinear",angleLines:{display:!1},beginAtZero:!0,grid:{circular:!0},pointLabels:{display:!1},startAngle:0}}};constructor(t,e){super(t,e),this.innerRadius=void 0,this.outerRadius=void 0}getLabelAndValue(t){const e=this._cachedMeta,i=this.chart,s=i.data.labels||[],n=ne(e._parsed[t].r,i.options.locale);return{label:s[t]||"",value:n}}parseObjectData(t,e,i,s){return ii.bind(this)(t,e,i,s)}update(t){const e=this._cachedMeta.data;this._updateRadius(),this.updateElements(e,0,e.length,t)}getMinMax(){const t=this._cachedMeta,e={min:Number.POSITIVE_INFINITY,max:Number.NEGATIVE_INFINITY};return t.data.forEach(((t,i)=>{const s=this.getParsed(i).r;!isNaN(s)&&this.chart.getDataVisibility(i)&&(se.max&&(e.max=s))})),e}_updateRadius(){const t=this.chart,e=t.chartArea,i=t.options,s=Math.min(e.right-e.left,e.bottom-e.top),n=Math.max(s/2,0),o=(n-Math.max(i.cutoutPercentage?n/100*i.cutoutPercentage:1,0))/t.getVisibleDatasetCount();this.outerRadius=n-o*this.index,this.innerRadius=this.outerRadius-o}updateElements(t,e,i,s){const n="reset"===s,o=this.chart,a=o.options.animation,r=this._cachedMeta.rScale,l=r.xCenter,h=r.yCenter,c=r.getIndexAngle(0)-.5*C;let d,u=c;const f=360/this.countVisibleElements();for(d=0;d{!isNaN(this.getParsed(i).r)&&this.chart.getDataVisibility(i)&&e++})),e}_computeAngle(t,e,i){return this.chart.getDataVisibility(t)?$(this.resolveDataElementOptions(t,e).angle||i):0}}var Un=Object.freeze({__proto__:null,BarController:class extends js{static id="bar";static defaults={datasetElementType:!1,dataElementType:"bar",categoryPercentage:.8,barPercentage:.9,grouped:!0,animations:{numbers:{type:"number",properties:["x","y","base","width","height"]}}};static overrides={scales:{_index_:{type:"category",offset:!0,grid:{offset:!0}},_value_:{type:"linear",beginAtZero:!0}}};parsePrimitiveData(t,e,i,s){return Vn(t,e,i,s)}parseArrayData(t,e,i,s){return Vn(t,e,i,s)}parseObjectData(t,e,i,s){const{iScale:n,vScale:o}=t,{xAxisKey:a="x",yAxisKey:r="y"}=this._parsing,l="x"===n.axis?a:r,h="x"===o.axis?a:r,c=[];let d,u,f,g;for(d=i,u=i+s;dt.controller.options.grouped)),o=i.options.stacked,a=[],r=this._cachedMeta.controller.getParsed(e),l=r&&r[i.axis],h=t=>{const e=t._parsed.find((t=>t[i.axis]===l)),n=e&&e[t.vScale.axis];if(s(n)||isNaN(n))return!0};for(const i of n)if((void 0===e||!h(i))&&((!1===o||-1===a.indexOf(i.stack)||void 0===o&&void 0===i.stack)&&a.push(i.stack),i.index===t))break;return a.length||a.push(void 0),a}_getStackCount(t){return this._getStacks(void 0,t).length}_getAxisCount(){return this._getAxis().length}getFirstScaleIdForIndexAxis(){const t=this.chart.scales,e=this.chart.options.indexAxis;return Object.keys(t).filter((i=>t[i].axis===e)).shift()}_getAxis(){const t={},e=this.getFirstScaleIdForIndexAxis();for(const i of this.chart.data.datasets)t[l("x"===this.chart.options.indexAxis?i.xAxisID:i.yAxisID,e)]=!0;return Object.keys(t)}_getStackIndex(t,e,i){const s=this._getStacks(t,i),n=void 0!==e?s.indexOf(e):-1;return-1===n?s.length-1:n}_getRuler(){const t=this.options,e=this._cachedMeta,i=e.iScale,s=[];let n,o;for(n=0,o=e.data.length;n=i?1:-1)}(u,e,r)*a,f===r&&(x-=u/2);const t=e.getPixelForDecimal(0),s=e.getPixelForDecimal(1),o=Math.min(t,s),h=Math.max(t,s);x=Math.max(Math.min(x,h),o),d=x+u,i&&!c&&(l._stacks[e.axis]._visualValues[n]=e.getValueForPixel(d)-e.getValueForPixel(x))}if(x===e.getPixelForValue(r)){const t=F(u)*e.getLineWidthForValue(r)/2;x+=t,u-=t}return{size:u,base:x,head:d,center:d+u/2}}_calculateBarIndexPixels(t,e){const i=e.scale,n=this.options,o=n.skipNull,a=l(n.maxBarThickness,1/0);let r,h;const c=this._getAxisCount();if(e.grouped){const i=o?this._getStackCount(t):e.stackCount,d="flex"===n.barThickness?function(t,e,i,s){const n=e.pixels,o=n[t];let a=t>0?n[t-1]:null,r=t=0;--i)e=Math.max(e,t[i].size(this.resolveDataElementOptions(i))/2);return e>0&&e}getLabelAndValue(t){const e=this._cachedMeta,i=this.chart.data.labels||[],{xScale:s,yScale:n}=e,o=this.getParsed(t),a=s.getLabelForValue(o.x),r=n.getLabelForValue(o.y),l=o._custom;return{label:i[t]||"",value:"("+a+", "+r+(l?", "+l:"")+")"}}update(t){const e=this._cachedMeta.data;this.updateElements(e,0,e.length,t)}updateElements(t,e,i,s){const n="reset"===s,{iScale:o,vScale:a}=this._cachedMeta,{sharedOptions:r,includeOptions:l}=this._getSharedOptions(e,s),h=o.axis,c=a.axis;for(let d=e;d0&&this.getParsed(e-1);for(let i=0;i<_;++i){const g=t[i],_=x?g:{};if(i=b){_.skip=!0;continue}const v=this.getParsed(i),M=s(v[f]),w=_[u]=a.getPixelForValue(v[u],i),k=_[f]=o||M?r.getBasePixel():r.getPixelForValue(l?this.applyStack(r,v,l):v[f],i);_.skip=isNaN(w)||isNaN(k)||M,_.stop=i>0&&Math.abs(v[u]-y[u])>m,p&&(_.parsed=v,_.raw=h.data[i]),d&&(_.options=c||this.resolveDataElementOptions(i,g.active?"active":n)),x||this.updateElement(g,i,_,n),y=v}}getMaxOverflow(){const t=this._cachedMeta,e=t.dataset,i=e.options&&e.options.borderWidth||0,s=t.data||[];if(!s.length)return i;const n=s[0].size(this.resolveDataElementOptions(0)),o=s[s.length-1].size(this.resolveDataElementOptions(s.length-1));return Math.max(i,n,o)/2}draw(){const t=this._cachedMeta;t.dataset.updateControlPoints(this.chart.chartArea,t.iScale.axis),super.draw()}},PieController:class extends $n{static id="pie";static defaults={cutout:0,rotation:0,circumference:360,radius:"100%"}},PolarAreaController:Yn,RadarController:class extends js{static id="radar";static defaults={datasetElementType:"line",dataElementType:"point",indexAxis:"r",showLine:!0,elements:{line:{fill:"start"}}};static overrides={aspectRatio:1,scales:{r:{type:"radialLinear"}}};getLabelAndValue(t){const e=this._cachedMeta.vScale,i=this.getParsed(t);return{label:e.getLabels()[t],value:""+e.getLabelForValue(i[e.axis])}}parseObjectData(t,e,i,s){return ii.bind(this)(t,e,i,s)}update(t){const e=this._cachedMeta,i=e.dataset,s=e.data||[],n=e.iScale.getLabels();if(i.points=s,"resize"!==t){const e=this.resolveDatasetElementOptions(t);this.options.showLine||(e.borderWidth=0);const o={_loop:!0,_fullLoop:n.length===s.length,options:e};this.updateElement(i,void 0,o,t)}this.updateElements(s,0,s.length,t)}updateElements(t,e,i,s){const n=this._cachedMeta.rScale,o="reset"===s;for(let a=e;a0&&this.getParsed(e-1);for(let c=e;c0&&Math.abs(i[f]-_[f])>x,m&&(p.parsed=i,p.raw=h.data[c]),u&&(p.options=d||this.resolveDataElementOptions(c,e.active?"active":n)),b||this.updateElement(e,c,p,n),_=i}this.updateSharedOptions(d,n,c)}getMaxOverflow(){const t=this._cachedMeta,e=t.data||[];if(!this.options.showLine){let t=0;for(let i=e.length-1;i>=0;--i)t=Math.max(t,e[i].size(this.resolveDataElementOptions(i))/2);return t>0&&t}const i=t.dataset,s=i.options&&i.options.borderWidth||0;if(!e.length)return s;const n=e[0].size(this.resolveDataElementOptions(0)),o=e[e.length-1].size(this.resolveDataElementOptions(e.length-1));return Math.max(s,n,o)/2}}});function Xn(t,e,i,s){const n=vi(t.options.borderRadius,["outerStart","outerEnd","innerStart","innerEnd"]);const o=(i-e)/2,a=Math.min(o,s*e/2),r=t=>{const e=(i-Math.min(o,t))*s/2;return Z(t,0,Math.min(o,e))};return{outerStart:r(n.outerStart),outerEnd:r(n.outerEnd),innerStart:Z(n.innerStart,0,a),innerEnd:Z(n.innerEnd,0,a)}}function qn(t,e,i,s){return{x:i+t*Math.cos(e),y:s+t*Math.sin(e)}}function Kn(t,e,i,s,n,o){const{x:a,y:r,startAngle:l,pixelMargin:h,innerRadius:c}=e,d=Math.max(e.outerRadius+s+i-h,0),u=c>0?c+s+i+h:0;let f=0;const g=n-l;if(s){const t=((c>0?c-s:0)+(d>0?d-s:0))/2;f=(g-(0!==t?g*t/(t+s):g))/2}const p=(g-Math.max(.001,g*d-i/C)/d)/2,m=l+p+f,x=n-p-f,{outerStart:b,outerEnd:_,innerStart:y,innerEnd:v}=Xn(e,u,d,x-m),M=d-b,w=d-_,k=m+b/M,S=x-_/w,P=u+y,D=u+v,O=m+y/P,A=x-v/D;if(t.beginPath(),o){const e=(k+S)/2;if(t.arc(a,r,d,k,e),t.arc(a,r,d,e,S),_>0){const e=qn(w,S,a,r);t.arc(e.x,e.y,_,S,x+E)}const i=qn(D,x,a,r);if(t.lineTo(i.x,i.y),v>0){const e=qn(D,A,a,r);t.arc(e.x,e.y,v,x+E,A+Math.PI)}const s=(x-v/u+(m+y/u))/2;if(t.arc(a,r,u,x-v/u,s,!0),t.arc(a,r,u,s,m+y/u,!0),y>0){const e=qn(P,O,a,r);t.arc(e.x,e.y,y,O+Math.PI,m-E)}const n=qn(M,m,a,r);if(t.lineTo(n.x,n.y),b>0){const e=qn(M,k,a,r);t.arc(e.x,e.y,b,m-E,k)}}else{t.moveTo(a,r);const e=Math.cos(k)*d+a,i=Math.sin(k)*d+r;t.lineTo(e,i);const s=Math.cos(S)*d+a,n=Math.sin(S)*d+r;t.lineTo(s,n)}t.closePath()}function Gn(t,e,i,s,n){const{fullCircles:o,startAngle:a,circumference:r,options:l}=e,{borderWidth:h,borderJoinStyle:c,borderDash:d,borderDashOffset:u,borderRadius:f}=l,g="inner"===l.borderAlign;if(!h)return;t.setLineDash(d||[]),t.lineDashOffset=u,g?(t.lineWidth=2*h,t.lineJoin=c||"round"):(t.lineWidth=h,t.lineJoin=c||"bevel");let p=e.endAngle;if(o){Kn(t,e,i,s,p,n);for(let e=0;en?(h=n/l,t.arc(o,a,l,i+h,s-h,!0)):t.arc(o,a,n,i+E,s-E),t.closePath(),t.clip()}(t,e,p),l.selfJoin&&p-a>=C&&0===f&&"miter"!==c&&function(t,e,i){const{startAngle:s,x:n,y:o,outerRadius:a,innerRadius:r,options:l}=e,{borderWidth:h,borderJoinStyle:c}=l,d=Math.min(h/a,G(s-i));if(t.beginPath(),t.arc(n,o,a-h/2,s+d/2,i-d/2),r>0){const e=Math.min(h/r,G(s-i));t.arc(n,o,r+h/2,i-e/2,s+e/2,!0)}else{const e=Math.min(h/2,a*G(s-i));if("round"===c)t.arc(n,o,e,i-C/2,s+C/2,!0);else if("bevel"===c){const a=2*e*e,r=-a*Math.cos(i+C/2)+n,l=-a*Math.sin(i+C/2)+o,h=a*Math.cos(s+C/2)+n,c=a*Math.sin(s+C/2)+o;t.lineTo(r,l),t.lineTo(h,c)}}t.closePath(),t.moveTo(0,0),t.rect(0,0,t.canvas.width,t.canvas.height),t.clip("evenodd")}(t,e,p),o||(Kn(t,e,i,s,p,n),t.stroke())}function Jn(t,e,i=e){t.lineCap=l(i.borderCapStyle,e.borderCapStyle),t.setLineDash(l(i.borderDash,e.borderDash)),t.lineDashOffset=l(i.borderDashOffset,e.borderDashOffset),t.lineJoin=l(i.borderJoinStyle,e.borderJoinStyle),t.lineWidth=l(i.borderWidth,e.borderWidth),t.strokeStyle=l(i.borderColor,e.borderColor)}function Zn(t,e,i){t.lineTo(i.x,i.y)}function Qn(t,e,i={}){const s=t.length,{start:n=0,end:o=s-1}=i,{start:a,end:r}=e,l=Math.max(n,a),h=Math.min(o,r),c=nr&&o>r;return{count:s,start:l,loop:e.loop,ilen:h(a+(h?r-t:t))%o,_=()=>{f!==g&&(t.lineTo(m,g),t.lineTo(m,f),t.lineTo(m,p))};for(l&&(d=n[b(0)],t.moveTo(d.x,d.y)),c=0;c<=r;++c){if(d=n[b(c)],d.skip)continue;const e=d.x,i=d.y,s=0|e;s===u?(ig&&(g=i),m=(x*m+e)/++x):(_(),t.lineTo(e,i),u=s,x=0,f=g=i),p=i}_()}function io(t){const e=t.options,i=e.borderDash&&e.borderDash.length;return!(t._decimated||t._loop||e.tension||"monotone"===e.cubicInterpolationMode||e.stepped||i)?eo:to}const so="function"==typeof Path2D;function no(t,e,i,s){so&&!e.options.segment?function(t,e,i,s){let n=e._path;n||(n=e._path=new Path2D,e.path(n,i,s)&&n.closePath()),Jn(t,e.options),t.stroke(n)}(t,e,i,s):function(t,e,i,s){const{segments:n,options:o}=e,a=io(e);for(const r of n)Jn(t,o,r.style),t.beginPath(),a(t,e,r,{start:i,end:i+s-1})&&t.closePath(),t.stroke()}(t,e,i,s)}class oo extends $s{static id="line";static defaults={borderCapStyle:"butt",borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",borderWidth:3,capBezierPoints:!0,cubicInterpolationMode:"default",fill:!1,spanGaps:!1,stepped:!1,tension:0};static defaultRoutes={backgroundColor:"backgroundColor",borderColor:"borderColor"};static descriptors={_scriptable:!0,_indexable:t=>"borderDash"!==t&&"fill"!==t};constructor(t){super(),this.animated=!0,this.options=void 0,this._chart=void 0,this._loop=void 0,this._fullLoop=void 0,this._path=void 0,this._points=void 0,this._segments=void 0,this._decimated=!1,this._pointsUpdated=!1,this._datasetIndex=void 0,t&&Object.assign(this,t)}updateControlPoints(t,e){const i=this.options;if((i.tension||"monotone"===i.cubicInterpolationMode)&&!i.stepped&&!this._pointsUpdated){const s=i.spanGaps?this._loop:this._fullLoop;hi(this._points,i,t,s,e),this._pointsUpdated=!0}}set points(t){this._points=t,delete this._segments,delete this._path,this._pointsUpdated=!1}get points(){return this._points}get segments(){return this._segments||(this._segments=zi(this,this.options.segment))}first(){const t=this.segments,e=this.points;return t.length&&e[t[0].start]}last(){const t=this.segments,e=this.points,i=t.length;return i&&e[t[i-1].end]}interpolate(t,e){const i=this.options,s=t[e],n=this.points,o=Ii(this,{property:e,start:s,end:s});if(!o.length)return;const a=[],r=function(t){return t.stepped?pi:t.tension||"monotone"===t.cubicInterpolationMode?mi:gi}(i);let l,h;for(l=0,h=o.length;l"borderDash"!==t};circumference;endAngle;fullCircles;innerRadius;outerRadius;pixelMargin;startAngle;constructor(t){super(),this.options=void 0,this.circumference=void 0,this.startAngle=void 0,this.endAngle=void 0,this.innerRadius=void 0,this.outerRadius=void 0,this.pixelMargin=0,this.fullCircles=0,t&&Object.assign(this,t)}inRange(t,e,i){const s=this.getProps(["x","y"],i),{angle:n,distance:o}=X(s,{x:t,y:e}),{startAngle:a,endAngle:r,innerRadius:h,outerRadius:c,circumference:d}=this.getProps(["startAngle","endAngle","innerRadius","outerRadius","circumference"],i),u=(this.options.spacing+this.options.borderWidth)/2,f=l(d,r-a),g=J(n,a,r)&&a!==r,p=f>=O||g,m=tt(o,h+u,c+u);return p&&m}getCenterPoint(t){const{x:e,y:i,startAngle:s,endAngle:n,innerRadius:o,outerRadius:a}=this.getProps(["x","y","startAngle","endAngle","innerRadius","outerRadius"],t),{offset:r,spacing:l}=this.options,h=(s+n)/2,c=(o+a+l+r)/2;return{x:e+Math.cos(h)*c,y:i+Math.sin(h)*c}}tooltipPosition(t){return this.getCenterPoint(t)}draw(t){const{options:e,circumference:i}=this,s=(e.offset||0)/4,n=(e.spacing||0)/2,o=e.circular;if(this.pixelMargin="inner"===e.borderAlign?.33:0,this.fullCircles=i>O?Math.floor(i/O):0,0===i||this.innerRadius<0||this.outerRadius<0)return;t.save();const a=(this.startAngle+this.endAngle)/2;t.translate(Math.cos(a)*s,Math.sin(a)*s);const r=s*(1-Math.sin(Math.min(C,i||0)));t.fillStyle=e.backgroundColor,t.strokeStyle=e.borderColor,function(t,e,i,s,n){const{fullCircles:o,startAngle:a,circumference:r}=e;let l=e.endAngle;if(o){Kn(t,e,i,s,l,n);for(let e=0;e("string"==typeof e?(i=t.push(e)-1,s.unshift({index:i,label:e})):isNaN(e)&&(i=null),i))(t,e,i,s);return n!==t.lastIndexOf(e)?i:n}function mo(t){const e=this.getLabels();return t>=0&&ts=e?s:t,a=t=>n=i?n:t;if(t){const t=F(s),e=F(n);t<0&&e<0?a(0):t>0&&e>0&&o(0)}if(s===n){let e=0===n?1:Math.abs(.05*n);a(n+e),t||o(s-e)}this.min=s,this.max=n}getTickLimit(){const t=this.options.ticks;let e,{maxTicksLimit:i,stepSize:s}=t;return s?(e=Math.ceil(this.max/s)-Math.floor(this.min/s)+1,e>1e3&&(console.warn(`scales.${this.id}.ticks.stepSize: ${s} would result generating up to ${e} ticks. Limiting to 1000.`),e=1e3)):(e=this.computeTickLimit(),i=i||11),i&&(e=Math.min(i,e)),e}computeTickLimit(){return Number.POSITIVE_INFINITY}buildTicks(){const t=this.options,e=t.ticks;let i=this.getTickLimit();i=Math.max(2,i);const n=function(t,e){const i=[],{bounds:n,step:o,min:a,max:r,precision:l,count:h,maxTicks:c,maxDigits:d,includeBounds:u}=t,f=o||1,g=c-1,{min:p,max:m}=e,x=!s(a),b=!s(r),_=!s(h),y=(m-p)/(d+1);let v,M,w,k,S=B((m-p)/g/f)*f;if(S<1e-14&&!x&&!b)return[{value:p},{value:m}];k=Math.ceil(m/S)-Math.floor(p/S),k>g&&(S=B(k*S/g/f)*f),s(l)||(v=Math.pow(10,l),S=Math.ceil(S*v)/v),"ticks"===n?(M=Math.floor(p/S)*S,w=Math.ceil(m/S)*S):(M=p,w=m),x&&b&&o&&H((r-a)/o,S/1e3)?(k=Math.round(Math.min((r-a)/S,c)),S=(r-a)/k,M=a,w=r):_?(M=x?a:M,w=b?r:w,k=h-1,S=(w-M)/k):(k=(w-M)/S,k=V(k,Math.round(k),S/1e3)?Math.round(k):Math.ceil(k));const P=Math.max(U(S),U(M));v=Math.pow(10,s(l)?P:l),M=Math.round(M*v)/v,w=Math.round(w*v)/v;let D=0;for(x&&(u&&M!==a?(i.push({value:a}),Mr)break;i.push({value:t})}return b&&u&&w!==r?i.length&&V(i[i.length-1].value,r,xo(r,y,t))?i[i.length-1].value=r:i.push({value:r}):b&&w!==r||i.push({value:w}),i}({maxTicks:i,bounds:t.bounds,min:t.min,max:t.max,precision:e.precision,step:e.stepSize,count:e.count,maxDigits:this._maxDigits(),horizontal:this.isHorizontal(),minRotation:e.minRotation||0,includeBounds:!1!==e.includeBounds},this._range||this);return"ticks"===t.bounds&&j(n,this,"value"),t.reverse?(n.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),n}configure(){const t=this.ticks;let e=this.min,i=this.max;if(super.configure(),this.options.offset&&t.length){const s=(i-e)/Math.max(t.length-1,1)/2;e-=s,i+=s}this._startValue=e,this._endValue=i,this._valueRange=i-e}getLabelForValue(t){return ne(t,this.chart.options.locale,this.options.ticks.format)}}class _o extends bo{static id="linear";static defaults={ticks:{callback:ae.formatters.numeric}};determineDataLimits(){const{min:t,max:e}=this.getMinMax(!0);this.min=a(t)?t:0,this.max=a(e)?e:1,this.handleTickRangeOptions()}computeTickLimit(){const t=this.isHorizontal(),e=t?this.width:this.height,i=$(this.options.ticks.minRotation),s=(t?Math.sin(i):Math.cos(i))||.001,n=this._resolveTickFontOptions(0);return Math.ceil(e/Math.min(40,n.lineHeight/s))}getPixelForValue(t){return null===t?NaN:this.getPixelForDecimal((t-this._startValue)/this._valueRange)}getValueForPixel(t){return this._startValue+this.getDecimalForPixel(t)*this._valueRange}}const yo=t=>Math.floor(z(t)),vo=(t,e)=>Math.pow(10,yo(t)+e);function Mo(t){return 1===t/Math.pow(10,yo(t))}function wo(t,e,i){const s=Math.pow(10,i),n=Math.floor(t/s);return Math.ceil(e/s)-n}function ko(t,{min:e,max:i}){e=r(t.min,e);const s=[],n=yo(e);let o=function(t,e){let i=yo(e-t);for(;wo(t,e,i)>10;)i++;for(;wo(t,e,i)<10;)i--;return Math.min(i,yo(t))}(e,i),a=o<0?Math.pow(10,Math.abs(o)):1;const l=Math.pow(10,o),h=n>o?Math.pow(10,n):0,c=Math.round((e-h)*a)/a,d=Math.floor((e-h)/l/10)*l*10;let u=Math.floor((c-d)/Math.pow(10,o)),f=r(t.min,Math.round((h+d+u*Math.pow(10,o))*a)/a);for(;f=10?u=u<15?15:20:u++,u>=20&&(o++,u=2,a=o>=0?1:a),f=Math.round((h+d+u*Math.pow(10,o))*a)/a;const g=r(t.max,f);return s.push({value:g,major:Mo(g),significand:u}),s}class So extends tn{static id="logarithmic";static defaults={ticks:{callback:ae.formatters.logarithmic,major:{enabled:!0}}};constructor(t){super(t),this.start=void 0,this.end=void 0,this._startValue=void 0,this._valueRange=0}parse(t,e){const i=bo.prototype.parse.apply(this,[t,e]);if(0!==i)return a(i)&&i>0?i:null;this._zero=!0}determineDataLimits(){const{min:t,max:e}=this.getMinMax(!0);this.min=a(t)?Math.max(0,t):null,this.max=a(e)?Math.max(0,e):null,this.options.beginAtZero&&(this._zero=!0),this._zero&&this.min!==this._suggestedMin&&!a(this._userMin)&&(this.min=t===vo(this.min,0)?vo(this.min,-1):vo(this.min,0)),this.handleTickRangeOptions()}handleTickRangeOptions(){const{minDefined:t,maxDefined:e}=this.getUserBounds();let i=this.min,s=this.max;const n=e=>i=t?i:e,o=t=>s=e?s:t;i===s&&(i<=0?(n(1),o(10)):(n(vo(i,-1)),o(vo(s,1)))),i<=0&&n(vo(s,-1)),s<=0&&o(vo(i,1)),this.min=i,this.max=s}buildTicks(){const t=this.options,e=ko({min:this._userMin,max:this._userMax},this);return"ticks"===t.bounds&&j(e,this,"value"),t.reverse?(e.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),e}getLabelForValue(t){return void 0===t?"0":ne(t,this.chart.options.locale,this.options.ticks.format)}configure(){const t=this.min;super.configure(),this._startValue=z(t),this._valueRange=z(this.max)-z(t)}getPixelForValue(t){return void 0!==t&&0!==t||(t=this.min),null===t||isNaN(t)?NaN:this.getPixelForDecimal(t===this.min?0:(z(t)-this._startValue)/this._valueRange)}getValueForPixel(t){const e=this.getDecimalForPixel(t);return Math.pow(10,this._startValue+e*this._valueRange)}}function Po(t){const e=t.ticks;if(e.display&&t.display){const t=ki(e.backdropPadding);return l(e.font&&e.font.size,ue.font.size)+t.height}return 0}function Do(t,e,i,s,n){return t===s||t===n?{start:e-i/2,end:e+i/2}:tn?{start:e-i,end:e}:{start:e,end:e+i}}function Co(t){const e={l:t.left+t._padding.left,r:t.right-t._padding.right,t:t.top+t._padding.top,b:t.bottom-t._padding.bottom},i=Object.assign({},e),s=[],o=[],a=t._pointLabels.length,r=t.options.pointLabels,l=r.centerPointLabels?C/a:0;for(let u=0;ue.r&&(r=(s.end-e.r)/o,t.r=Math.max(t.r,e.r+r)),n.starte.b&&(l=(n.end-e.b)/a,t.b=Math.max(t.b,e.b+l))}function Ao(t,e,i){const s=t.drawingArea,{extra:n,additionalAngle:o,padding:a,size:r}=i,l=t.getPointPosition(e,s+n+a,o),h=Math.round(Y(G(l.angle+E))),c=function(t,e,i){90===i||270===i?t-=e/2:(i>270||i<90)&&(t-=e);return t}(l.y,r.h,h),d=function(t){if(0===t||180===t)return"center";if(t<180)return"left";return"right"}(h),u=function(t,e,i){"right"===i?t-=e:"center"===i&&(t-=e/2);return t}(l.x,r.w,d);return{visible:!0,x:l.x,y:c,textAlign:d,left:u,top:c,right:u+r.w,bottom:c+r.h}}function To(t,e){if(!e)return!0;const{left:i,top:s,right:n,bottom:o}=t;return!(Re({x:i,y:s},e)||Re({x:i,y:o},e)||Re({x:n,y:s},e)||Re({x:n,y:o},e))}function Lo(t,e,i){const{left:n,top:o,right:a,bottom:r}=i,{backdropColor:l}=e;if(!s(l)){const i=wi(e.borderRadius),s=ki(e.backdropPadding);t.fillStyle=l;const h=n-s.left,c=o-s.top,d=a-n+s.width,u=r-o+s.height;Object.values(i).some((t=>0!==t))?(t.beginPath(),He(t,{x:h,y:c,w:d,h:u,radius:i}),t.fill()):t.fillRect(h,c,d,u)}}function Eo(t,e,i,s){const{ctx:n}=t;if(i)n.arc(t.xCenter,t.yCenter,e,0,O);else{let i=t.getPointPosition(0,e);n.moveTo(i.x,i.y);for(let o=1;ot,padding:5,centerPointLabels:!1}};static defaultRoutes={"angleLines.color":"borderColor","pointLabels.color":"color","ticks.color":"color"};static descriptors={angleLines:{_fallback:"grid"}};constructor(t){super(t),this.xCenter=void 0,this.yCenter=void 0,this.drawingArea=void 0,this._pointLabels=[],this._pointLabelItems=[]}setDimensions(){const t=this._padding=ki(Po(this.options)/2),e=this.width=this.maxWidth-t.width,i=this.height=this.maxHeight-t.height;this.xCenter=Math.floor(this.left+e/2+t.left),this.yCenter=Math.floor(this.top+i/2+t.top),this.drawingArea=Math.floor(Math.min(e,i)/2)}determineDataLimits(){const{min:t,max:e}=this.getMinMax(!1);this.min=a(t)&&!isNaN(t)?t:0,this.max=a(e)&&!isNaN(e)?e:0,this.handleTickRangeOptions()}computeTickLimit(){return Math.ceil(this.drawingArea/Po(this.options))}generateTickLabels(t){bo.prototype.generateTickLabels.call(this,t),this._pointLabels=this.getLabels().map(((t,e)=>{const i=d(this.options.pointLabels.callback,[t,e],this);return i||0===i?i:""})).filter(((t,e)=>this.chart.getDataVisibility(e)))}fit(){const t=this.options;t.display&&t.pointLabels.display?Co(this):this.setCenterPoint(0,0,0,0)}setCenterPoint(t,e,i,s){this.xCenter+=Math.floor((t-e)/2),this.yCenter+=Math.floor((i-s)/2),this.drawingArea-=Math.min(this.drawingArea/2,Math.max(t,e,i,s))}getIndexAngle(t){return G(t*(O/(this._pointLabels.length||1))+$(this.options.startAngle||0))}getDistanceFromCenterForValue(t){if(s(t))return NaN;const e=this.drawingArea/(this.max-this.min);return this.options.reverse?(this.max-t)*e:(t-this.min)*e}getValueForDistanceFromCenter(t){if(s(t))return NaN;const e=t/(this.drawingArea/(this.max-this.min));return this.options.reverse?this.max-e:this.min+e}getPointLabelContext(t){const e=this._pointLabels||[];if(t>=0&&t=0;n--){const e=t._pointLabelItems[n];if(!e.visible)continue;const o=s.setContext(t.getPointLabelContext(n));Lo(i,o,e);const a=Si(o.font),{x:r,y:l,textAlign:h}=e;Ne(i,t._pointLabels[n],r,l+a.lineHeight/2,a,{color:o.color,textAlign:h,textBaseline:"middle"})}}(this,o),s.display&&this.ticks.forEach(((t,e)=>{if(0!==e||0===e&&this.min<0){r=this.getDistanceFromCenterForValue(t.value);const i=this.getContext(e),a=s.setContext(i),l=n.setContext(i);!function(t,e,i,s,n){const o=t.ctx,a=e.circular,{color:r,lineWidth:l}=e;!a&&!s||!r||!l||i<0||(o.save(),o.strokeStyle=r,o.lineWidth=l,o.setLineDash(n.dash||[]),o.lineDashOffset=n.dashOffset,o.beginPath(),Eo(t,i,a,s),o.closePath(),o.stroke(),o.restore())}(this,a,r,o,l)}})),i.display){for(t.save(),a=o-1;a>=0;a--){const s=i.setContext(this.getPointLabelContext(a)),{color:n,lineWidth:o}=s;o&&n&&(t.lineWidth=o,t.strokeStyle=n,t.setLineDash(s.borderDash),t.lineDashOffset=s.borderDashOffset,r=this.getDistanceFromCenterForValue(e.reverse?this.min:this.max),l=this.getPointPosition(a,r),t.beginPath(),t.moveTo(this.xCenter,this.yCenter),t.lineTo(l.x,l.y),t.stroke())}t.restore()}}drawBorder(){}drawLabels(){const t=this.ctx,e=this.options,i=e.ticks;if(!i.display)return;const s=this.getIndexAngle(0);let n,o;t.save(),t.translate(this.xCenter,this.yCenter),t.rotate(s),t.textAlign="center",t.textBaseline="middle",this.ticks.forEach(((s,a)=>{if(0===a&&this.min>=0&&!e.reverse)return;const r=i.setContext(this.getContext(a)),l=Si(r.font);if(n=this.getDistanceFromCenterForValue(this.ticks[a].value),r.showLabelBackdrop){t.font=l.string,o=t.measureText(s.label).width,t.fillStyle=r.backdropColor;const e=ki(r.backdropPadding);t.fillRect(-o/2-e.left,-n-l.size/2-e.top,o+e.width,l.size+e.height)}Ne(t,s.label,0,-n,l,{color:r.color,strokeColor:r.textStrokeColor,strokeWidth:r.textStrokeWidth})})),t.restore()}drawTitle(){}}const Io={millisecond:{common:!0,size:1,steps:1e3},second:{common:!0,size:1e3,steps:60},minute:{common:!0,size:6e4,steps:60},hour:{common:!0,size:36e5,steps:24},day:{common:!0,size:864e5,steps:30},week:{common:!1,size:6048e5,steps:4},month:{common:!0,size:2628e6,steps:12},quarter:{common:!1,size:7884e6,steps:4},year:{common:!0,size:3154e7}},zo=Object.keys(Io);function Fo(t,e){return t-e}function Vo(t,e){if(s(e))return null;const i=t._adapter,{parser:n,round:o,isoWeekday:r}=t._parseOpts;let l=e;return"function"==typeof n&&(l=n(l)),a(l)||(l="string"==typeof n?i.parse(l,n):i.parse(l)),null===l?null:(o&&(l="week"!==o||!N(r)&&!0!==r?i.startOf(l,o):i.startOf(l,"isoWeek",r)),+l)}function Bo(t,e,i,s){const n=zo.length;for(let o=zo.indexOf(t);o=e?i[s]:i[n]]=!0}}else t[e]=!0}function No(t,e,i){const s=[],n={},o=e.length;let a,r;for(a=0;a=0&&(e[l].major=!0);return e}(t,s,n,i):s}class Ho extends tn{static id="time";static defaults={bounds:"data",adapters:{},time:{parser:!1,unit:!1,round:!1,isoWeekday:!1,minUnit:"millisecond",displayFormats:{}},ticks:{source:"auto",callback:!1,major:{enabled:!1}}};constructor(t){super(t),this._cache={data:[],labels:[],all:[]},this._unit="day",this._majorUnit=void 0,this._offsets={},this._normalized=!1,this._parseOpts=void 0}init(t,e={}){const i=t.time||(t.time={}),s=this._adapter=new In._date(t.adapters.date);s.init(e),b(i.displayFormats,s.formats()),this._parseOpts={parser:i.parser,round:i.round,isoWeekday:i.isoWeekday},super.init(t),this._normalized=e.normalized}parse(t,e){return void 0===t?null:Vo(this,t)}beforeLayout(){super.beforeLayout(),this._cache={data:[],labels:[],all:[]}}determineDataLimits(){const t=this.options,e=this._adapter,i=t.time.unit||"day";let{min:s,max:n,minDefined:o,maxDefined:r}=this.getUserBounds();function l(t){o||isNaN(t.min)||(s=Math.min(s,t.min)),r||isNaN(t.max)||(n=Math.max(n,t.max))}o&&r||(l(this._getLabelBounds()),"ticks"===t.bounds&&"labels"===t.ticks.source||l(this.getMinMax(!1))),s=a(s)&&!isNaN(s)?s:+e.startOf(Date.now(),i),n=a(n)&&!isNaN(n)?n:+e.endOf(Date.now(),i)+1,this.min=Math.min(s,n-1),this.max=Math.max(s+1,n)}_getLabelBounds(){const t=this.getLabelTimestamps();let e=Number.POSITIVE_INFINITY,i=Number.NEGATIVE_INFINITY;return t.length&&(e=t[0],i=t[t.length-1]),{min:e,max:i}}buildTicks(){const t=this.options,e=t.time,i=t.ticks,s="labels"===i.source?this.getLabelTimestamps():this._generate();"ticks"===t.bounds&&s.length&&(this.min=this._userMin||s[0],this.max=this._userMax||s[s.length-1]);const n=this.min,o=nt(s,n,this.max);return this._unit=e.unit||(i.autoSkip?Bo(e.minUnit,this.min,this.max,this._getLabelCapacity(n)):function(t,e,i,s,n){for(let o=zo.length-1;o>=zo.indexOf(i);o--){const i=zo[o];if(Io[i].common&&t._adapter.diff(n,s,i)>=e-1)return i}return zo[i?zo.indexOf(i):0]}(this,o.length,e.minUnit,this.min,this.max)),this._majorUnit=i.major.enabled&&"year"!==this._unit?function(t){for(let e=zo.indexOf(t)+1,i=zo.length;e+t.value)))}initOffsets(t=[]){let e,i,s=0,n=0;this.options.offset&&t.length&&(e=this.getDecimalForValue(t[0]),s=1===t.length?1-e:(this.getDecimalForValue(t[1])-e)/2,i=this.getDecimalForValue(t[t.length-1]),n=1===t.length?i:(i-this.getDecimalForValue(t[t.length-2]))/2);const o=t.length<3?.5:.25;s=Z(s,0,o),n=Z(n,0,o),this._offsets={start:s,end:n,factor:1/(s+1+n)}}_generate(){const t=this._adapter,e=this.min,i=this.max,s=this.options,n=s.time,o=n.unit||Bo(n.minUnit,e,i,this._getLabelCapacity(e)),a=l(s.ticks.stepSize,1),r="week"===o&&n.isoWeekday,h=N(r)||!0===r,c={};let d,u,f=e;if(h&&(f=+t.startOf(f,"isoWeek",r)),f=+t.startOf(f,h?"day":o),t.diff(i,e,o)>1e5*a)throw new Error(e+" and "+i+" are too far apart with stepSize of "+a+" "+o);const g="data"===s.ticks.source&&this.getDataTimestamps();for(d=f,u=0;d+t))}getLabelForValue(t){const e=this._adapter,i=this.options.time;return i.tooltipFormat?e.format(t,i.tooltipFormat):e.format(t,i.displayFormats.datetime)}format(t,e){const i=this.options.time.displayFormats,s=this._unit,n=e||i[s];return this._adapter.format(t,n)}_tickFormatFunction(t,e,i,s){const n=this.options,o=n.ticks.callback;if(o)return d(o,[t,e,i],this);const a=n.time.displayFormats,r=this._unit,l=this._majorUnit,h=r&&a[r],c=l&&a[l],u=i[e],f=l&&c&&u&&u.major;return this._adapter.format(t,s||(f?c:h))}generateTickLabels(t){let e,i,s;for(e=0,i=t.length;e0?a:1}getDataTimestamps(){let t,e,i=this._cache.data||[];if(i.length)return i;const s=this.getMatchingVisibleMetas();if(this._normalized&&s.length)return this._cache.data=s[0].controller.getAllParsedValues(this);for(t=0,e=s.length;t=t[r].pos&&e<=t[l].pos&&({lo:r,hi:l}=it(t,"pos",e)),({pos:s,time:o}=t[r]),({pos:n,time:a}=t[l])):(e>=t[r].time&&e<=t[l].time&&({lo:r,hi:l}=it(t,"time",e)),({time:s,pos:o}=t[r]),({time:n,pos:a}=t[l]));const h=n-s;return h?o+(a-o)*(e-s)/h:o}var $o=Object.freeze({__proto__:null,CategoryScale:class extends tn{static id="category";static defaults={ticks:{callback:mo}};constructor(t){super(t),this._startValue=void 0,this._valueRange=0,this._addedLabels=[]}init(t){const e=this._addedLabels;if(e.length){const t=this.getLabels();for(const{index:i,label:s}of e)t[i]===s&&t.splice(i,1);this._addedLabels=[]}super.init(t)}parse(t,e){if(s(t))return null;const i=this.getLabels();return((t,e)=>null===t?null:Z(Math.round(t),0,e))(e=isFinite(e)&&i[e]===t?e:po(i,t,l(e,t),this._addedLabels),i.length-1)}determineDataLimits(){const{minDefined:t,maxDefined:e}=this.getUserBounds();let{min:i,max:s}=this.getMinMax(!0);"ticks"===this.options.bounds&&(t||(i=0),e||(s=this.getLabels().length-1)),this.min=i,this.max=s}buildTicks(){const t=this.min,e=this.max,i=this.options.offset,s=[];let n=this.getLabels();n=0===t&&e===n.length-1?n:n.slice(t,e+1),this._valueRange=Math.max(n.length-(i?0:1),1),this._startValue=this.min-(i?.5:0);for(let i=t;i<=e;i++)s.push({value:i});return s}getLabelForValue(t){return mo.call(this,t)}configure(){super.configure(),this.isHorizontal()||(this._reversePixels=!this._reversePixels)}getPixelForValue(t){return"number"!=typeof t&&(t=this.parse(t)),null===t?NaN:this.getPixelForDecimal((t-this._startValue)/this._valueRange)}getPixelForTick(t){const e=this.ticks;return t<0||t>e.length-1?null:this.getPixelForValue(e[t].value)}getValueForPixel(t){return Math.round(this._startValue+this.getDecimalForPixel(t)*this._valueRange)}getBasePixel(){return this.bottom}},LinearScale:_o,LogarithmicScale:So,RadialLinearScale:Ro,TimeScale:Ho,TimeSeriesScale:class extends Ho{static id="timeseries";static defaults=Ho.defaults;constructor(t){super(t),this._table=[],this._minPos=void 0,this._tableRange=void 0}initOffsets(){const t=this._getTimestampsForTable(),e=this._table=this.buildLookupTable(t);this._minPos=jo(e,this.min),this._tableRange=jo(e,this.max)-this._minPos,super.initOffsets(t)}buildLookupTable(t){const{min:e,max:i}=this,s=[],n=[];let o,a,r,l,h;for(o=0,a=t.length;o=e&&l<=i&&s.push(l);if(s.length<2)return[{time:e,pos:0},{time:i,pos:1}];for(o=0,a=s.length;ot-e))}_getTimestampsForTable(){let t=this._cache.all||[];if(t.length)return t;const e=this.getDataTimestamps(),i=this.getLabelTimestamps();return t=e.length&&i.length?this.normalize(e.concat(i)):e.length?e:i,t=this._cache.all=t,t}getDecimalForValue(t){return(jo(this._table,t)-this._minPos)/this._tableRange}getValueForPixel(t){const e=this._offsets,i=this.getDecimalForPixel(t)/e.factor-e.end;return jo(this._table,i*this._tableRange+this._minPos,!0)}}});const Yo=["rgb(54, 162, 235)","rgb(255, 99, 132)","rgb(255, 159, 64)","rgb(255, 205, 86)","rgb(75, 192, 192)","rgb(153, 102, 255)","rgb(201, 203, 207)"],Uo=Yo.map((t=>t.replace("rgb(","rgba(").replace(")",", 0.5)")));function Xo(t){return Yo[t%Yo.length]}function qo(t){return Uo[t%Uo.length]}function Ko(t){let e=0;return(i,s)=>{const n=t.getDatasetMeta(s).controller;n instanceof $n?e=function(t,e){return t.backgroundColor=t.data.map((()=>Xo(e++))),e}(i,e):n instanceof Yn?e=function(t,e){return t.backgroundColor=t.data.map((()=>qo(e++))),e}(i,e):n&&(e=function(t,e){return t.borderColor=Xo(e),t.backgroundColor=qo(e),++e}(i,e))}}function Go(t){let e;for(e in t)if(t[e].borderColor||t[e].backgroundColor)return!0;return!1}var Jo={id:"colors",defaults:{enabled:!0,forceOverride:!1},beforeLayout(t,e,i){if(!i.enabled)return;const{data:{datasets:s},options:n}=t.config,{elements:o}=n,a=Go(s)||(r=n)&&(r.borderColor||r.backgroundColor)||o&&Go(o)||"rgba(0,0,0,0.1)"!==ue.borderColor||"rgba(0,0,0,0.1)"!==ue.backgroundColor;var r;if(!i.forceOverride&&a)return;const l=Ko(t);s.forEach(l)}};function Zo(t){if(t._decimated){const e=t._data;delete t._decimated,delete t._data,Object.defineProperty(t,"data",{configurable:!0,enumerable:!0,writable:!0,value:e})}}function Qo(t){t.data.datasets.forEach((t=>{Zo(t)}))}var ta={id:"decimation",defaults:{algorithm:"min-max",enabled:!1},beforeElementsUpdate:(t,e,i)=>{if(!i.enabled)return void Qo(t);const n=t.width;t.data.datasets.forEach(((e,o)=>{const{_data:a,indexAxis:r}=e,l=t.getDatasetMeta(o),h=a||e.data;if("y"===Pi([r,t.options.indexAxis]))return;if(!l.controller.supportsDecimation)return;const c=t.scales[l.xAxisID];if("linear"!==c.type&&"time"!==c.type)return;if(t.options.parsing)return;let{start:d,count:u}=function(t,e){const i=e.length;let s,n=0;const{iScale:o}=t,{min:a,max:r,minDefined:l,maxDefined:h}=o.getUserBounds();return l&&(n=Z(it(e,o.axis,a).lo,0,i-1)),s=h?Z(it(e,o.axis,r).hi+1,n,i)-n:i-n,{start:n,count:s}}(l,h);if(u<=(i.threshold||4*n))return void Zo(e);let f;switch(s(a)&&(e._data=h,delete e.data,Object.defineProperty(e,"data",{configurable:!0,enumerable:!0,get:function(){return this._decimated},set:function(t){this._data=t}})),i.algorithm){case"lttb":f=function(t,e,i,s,n){const o=n.samples||s;if(o>=i)return t.slice(e,e+i);const a=[],r=(i-2)/(o-2);let l=0;const h=e+i-1;let c,d,u,f,g,p=e;for(a[l++]=t[p],c=0;cu&&(u=f,d=t[s],g=s);a[l++]=d,p=g}return a[l++]=t[h],a}(h,d,u,n,i);break;case"min-max":f=function(t,e,i,n){let o,a,r,l,h,c,d,u,f,g,p=0,m=0;const x=[],b=e+i-1,_=t[e].x,y=t[b].x-_;for(o=e;og&&(g=l,d=o),p=(m*p+a.x)/++m;else{const i=o-1;if(!s(c)&&!s(d)){const e=Math.min(c,d),s=Math.max(c,d);e!==u&&e!==i&&x.push({...t[e],x:p}),s!==u&&s!==i&&x.push({...t[s],x:p})}o>0&&i!==u&&x.push(t[i]),x.push(a),h=e,m=0,f=g=l,c=d=u=o}}return x}(h,d,u,n);break;default:throw new Error(`Unsupported decimation algorithm '${i.algorithm}'`)}e._decimated=f}))},destroy(t){Qo(t)}};function ea(t,e,i,s){if(s)return;let n=e[t],o=i[t];return"angle"===t&&(n=G(n),o=G(o)),{property:t,start:n,end:o}}function ia(t,e,i){for(;e>t;e--){const t=i[e];if(!isNaN(t.x)&&!isNaN(t.y))break}return e}function sa(t,e,i,s){return t&&e?s(t[i],e[i]):t?t[i]:e?e[i]:0}function na(t,e){let i=[],s=!1;return n(t)?(s=!0,i=t):i=function(t,e){const{x:i=null,y:s=null}=t||{},n=e.points,o=[];return e.segments.forEach((({start:t,end:e})=>{e=ia(t,e,n);const a=n[t],r=n[e];null!==s?(o.push({x:a.x,y:s}),o.push({x:r.x,y:s})):null!==i&&(o.push({x:i,y:a.y}),o.push({x:i,y:r.y}))})),o}(t,e),i.length?new oo({points:i,options:{tension:0},_loop:s,_fullLoop:s}):null}function oa(t){return t&&!1!==t.fill}function aa(t,e,i){let s=t[e].fill;const n=[e];let o;if(!i)return s;for(;!1!==s&&-1===n.indexOf(s);){if(!a(s))return s;if(o=t[s],!o)return!1;if(o.visible)return s;n.push(s),s=o.fill}return!1}function ra(t,e,i){const s=function(t){const e=t.options,i=e.fill;let s=l(i&&i.target,i);void 0===s&&(s=!!e.backgroundColor);if(!1===s||null===s)return!1;if(!0===s)return"origin";return s}(t);if(o(s))return!isNaN(s.value)&&s;let n=parseFloat(s);return a(n)&&Math.floor(n)===n?function(t,e,i,s){"-"!==t&&"+"!==t||(i=e+i);if(i===e||i<0||i>=s)return!1;return i}(s[0],e,n,i):["origin","start","end","stack","shape"].indexOf(s)>=0&&s}function la(t,e,i){const s=[];for(let n=0;n=0;--e){const i=n[e].$filler;i&&(i.line.updateControlPoints(o,i.axis),s&&i.fill&&ua(t.ctx,i,o))}},beforeDatasetsDraw(t,e,i){if("beforeDatasetsDraw"!==i.drawTime)return;const s=t.getSortedVisibleDatasetMetas();for(let e=s.length-1;e>=0;--e){const i=s[e].$filler;oa(i)&&ua(t.ctx,i,t.chartArea)}},beforeDatasetDraw(t,e,i){const s=e.meta.$filler;oa(s)&&"beforeDatasetDraw"===i.drawTime&&ua(t.ctx,s,t.chartArea)},defaults:{propagate:!0,drawTime:"beforeDatasetDraw"}};const _a=(t,e)=>{let{boxHeight:i=e,boxWidth:s=e}=t;return t.usePointStyle&&(i=Math.min(i,e),s=t.pointStyleWidth||Math.min(s,e)),{boxWidth:s,boxHeight:i,itemHeight:Math.max(e,i)}};class ya extends $s{constructor(t){super(),this._added=!1,this.legendHitBoxes=[],this._hoveredItem=null,this.doughnutMode=!1,this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this.legendItems=void 0,this.columnSizes=void 0,this.lineWidths=void 0,this.maxHeight=void 0,this.maxWidth=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.height=void 0,this.width=void 0,this._margins=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,e,i){this.maxWidth=t,this.maxHeight=e,this._margins=i,this.setDimensions(),this.buildLabels(),this.fit()}setDimensions(){this.isHorizontal()?(this.width=this.maxWidth,this.left=this._margins.left,this.right=this.width):(this.height=this.maxHeight,this.top=this._margins.top,this.bottom=this.height)}buildLabels(){const t=this.options.labels||{};let e=d(t.generateLabels,[this.chart],this)||[];t.filter&&(e=e.filter((e=>t.filter(e,this.chart.data)))),t.sort&&(e=e.sort(((e,i)=>t.sort(e,i,this.chart.data)))),this.options.reverse&&e.reverse(),this.legendItems=e}fit(){const{options:t,ctx:e}=this;if(!t.display)return void(this.width=this.height=0);const i=t.labels,s=Si(i.font),n=s.size,o=this._computeTitleHeight(),{boxWidth:a,itemHeight:r}=_a(i,n);let l,h;e.font=s.string,this.isHorizontal()?(l=this.maxWidth,h=this._fitRows(o,n,a,r)+10):(h=this.maxHeight,l=this._fitCols(o,s,a,r)+10),this.width=Math.min(l,t.maxWidth||this.maxWidth),this.height=Math.min(h,t.maxHeight||this.maxHeight)}_fitRows(t,e,i,s){const{ctx:n,maxWidth:o,options:{labels:{padding:a}}}=this,r=this.legendHitBoxes=[],l=this.lineWidths=[0],h=s+a;let c=t;n.textAlign="left",n.textBaseline="middle";let d=-1,u=-h;return this.legendItems.forEach(((t,f)=>{const g=i+e/2+n.measureText(t.text).width;(0===f||l[l.length-1]+g+2*a>o)&&(c+=h,l[l.length-(f>0?0:1)]=0,u+=h,d++),r[f]={left:0,top:u,row:d,width:g,height:s},l[l.length-1]+=g+a})),c}_fitCols(t,e,i,s){const{ctx:n,maxHeight:o,options:{labels:{padding:a}}}=this,r=this.legendHitBoxes=[],l=this.columnSizes=[],h=o-t;let c=a,d=0,u=0,f=0,g=0;return this.legendItems.forEach(((t,o)=>{const{itemWidth:p,itemHeight:m}=function(t,e,i,s,n){const o=function(t,e,i,s){let n=t.text;n&&"string"!=typeof n&&(n=n.reduce(((t,e)=>t.length>e.length?t:e)));return e+i.size/2+s.measureText(n).width}(s,t,e,i),a=function(t,e,i){let s=t;"string"!=typeof e.text&&(s=va(e,i));return s}(n,s,e.lineHeight);return{itemWidth:o,itemHeight:a}}(i,e,n,t,s);o>0&&u+m+2*a>h&&(c+=d+a,l.push({width:d,height:u}),f+=d+a,g++,d=u=0),r[o]={left:f,top:u,col:g,width:p,height:m},d=Math.max(d,p),u+=m+a})),c+=d,l.push({width:d,height:u}),c}adjustHitBoxes(){if(!this.options.display)return;const t=this._computeTitleHeight(),{legendHitBoxes:e,options:{align:i,labels:{padding:s},rtl:n}}=this,o=Oi(n,this.left,this.width);if(this.isHorizontal()){let n=0,a=ft(i,this.left+s,this.right-this.lineWidths[n]);for(const r of e)n!==r.row&&(n=r.row,a=ft(i,this.left+s,this.right-this.lineWidths[n])),r.top+=this.top+t+s,r.left=o.leftForLtr(o.x(a),r.width),a+=r.width+s}else{let n=0,a=ft(i,this.top+t+s,this.bottom-this.columnSizes[n].height);for(const r of e)r.col!==n&&(n=r.col,a=ft(i,this.top+t+s,this.bottom-this.columnSizes[n].height)),r.top=a,r.left+=this.left+s,r.left=o.leftForLtr(o.x(r.left),r.width),a+=r.height+s}}isHorizontal(){return"top"===this.options.position||"bottom"===this.options.position}draw(){if(this.options.display){const t=this.ctx;Ie(t,this),this._draw(),ze(t)}}_draw(){const{options:t,columnSizes:e,lineWidths:i,ctx:s}=this,{align:n,labels:o}=t,a=ue.color,r=Oi(t.rtl,this.left,this.width),h=Si(o.font),{padding:c}=o,d=h.size,u=d/2;let f;this.drawTitle(),s.textAlign=r.textAlign("left"),s.textBaseline="middle",s.lineWidth=.5,s.font=h.string;const{boxWidth:g,boxHeight:p,itemHeight:m}=_a(o,d),x=this.isHorizontal(),b=this._computeTitleHeight();f=x?{x:ft(n,this.left+c,this.right-i[0]),y:this.top+c+b,line:0}:{x:this.left+c,y:ft(n,this.top+b+c,this.bottom-e[0].height),line:0},Ai(this.ctx,t.textDirection);const _=m+c;this.legendItems.forEach(((y,v)=>{s.strokeStyle=y.fontColor,s.fillStyle=y.fontColor;const M=s.measureText(y.text).width,w=r.textAlign(y.textAlign||(y.textAlign=o.textAlign)),k=g+u+M;let S=f.x,P=f.y;r.setWidth(this.width),x?v>0&&S+k+c>this.right&&(P=f.y+=_,f.line++,S=f.x=ft(n,this.left+c,this.right-i[f.line])):v>0&&P+_>this.bottom&&(S=f.x=S+e[f.line].width+c,f.line++,P=f.y=ft(n,this.top+b+c,this.bottom-e[f.line].height));if(function(t,e,i){if(isNaN(g)||g<=0||isNaN(p)||p<0)return;s.save();const n=l(i.lineWidth,1);if(s.fillStyle=l(i.fillStyle,a),s.lineCap=l(i.lineCap,"butt"),s.lineDashOffset=l(i.lineDashOffset,0),s.lineJoin=l(i.lineJoin,"miter"),s.lineWidth=n,s.strokeStyle=l(i.strokeStyle,a),s.setLineDash(l(i.lineDash,[])),o.usePointStyle){const a={radius:p*Math.SQRT2/2,pointStyle:i.pointStyle,rotation:i.rotation,borderWidth:n},l=r.xPlus(t,g/2);Ee(s,a,l,e+u,o.pointStyleWidth&&g)}else{const o=e+Math.max((d-p)/2,0),a=r.leftForLtr(t,g),l=wi(i.borderRadius);s.beginPath(),Object.values(l).some((t=>0!==t))?He(s,{x:a,y:o,w:g,h:p,radius:l}):s.rect(a,o,g,p),s.fill(),0!==n&&s.stroke()}s.restore()}(r.x(S),P,y),S=gt(w,S+g+u,x?S+k:this.right,t.rtl),function(t,e,i){Ne(s,i.text,t,e+m/2,h,{strikethrough:i.hidden,textAlign:r.textAlign(i.textAlign)})}(r.x(S),P,y),x)f.x+=k+c;else if("string"!=typeof y.text){const t=h.lineHeight;f.y+=va(y,t)+c}else f.y+=_})),Ti(this.ctx,t.textDirection)}drawTitle(){const t=this.options,e=t.title,i=Si(e.font),s=ki(e.padding);if(!e.display)return;const n=Oi(t.rtl,this.left,this.width),o=this.ctx,a=e.position,r=i.size/2,l=s.top+r;let h,c=this.left,d=this.width;if(this.isHorizontal())d=Math.max(...this.lineWidths),h=this.top+l,c=ft(t.align,c,this.right-d);else{const e=this.columnSizes.reduce(((t,e)=>Math.max(t,e.height)),0);h=l+ft(t.align,this.top,this.bottom-e-t.labels.padding-this._computeTitleHeight())}const u=ft(a,c,c+d);o.textAlign=n.textAlign(ut(a)),o.textBaseline="middle",o.strokeStyle=e.color,o.fillStyle=e.color,o.font=i.string,Ne(o,e.text,u,h,i)}_computeTitleHeight(){const t=this.options.title,e=Si(t.font),i=ki(t.padding);return t.display?e.lineHeight+i.height:0}_getLegendItemAt(t,e){let i,s,n;if(tt(t,this.left,this.right)&&tt(e,this.top,this.bottom))for(n=this.legendHitBoxes,i=0;it.chart.options.color,boxWidth:40,padding:10,generateLabels(t){const e=t.data.datasets,{labels:{usePointStyle:i,pointStyle:s,textAlign:n,color:o,useBorderRadius:a,borderRadius:r}}=t.legend.options;return t._getSortedDatasetMetas().map((t=>{const l=t.controller.getStyle(i?0:void 0),h=ki(l.borderWidth);return{text:e[t.index].label,fillStyle:l.backgroundColor,fontColor:o,hidden:!t.visible,lineCap:l.borderCapStyle,lineDash:l.borderDash,lineDashOffset:l.borderDashOffset,lineJoin:l.borderJoinStyle,lineWidth:(h.width+h.height)/4,strokeStyle:l.borderColor,pointStyle:s||l.pointStyle,rotation:l.rotation,textAlign:n||l.textAlign,borderRadius:a&&(r||l.borderRadius),datasetIndex:t.index}}),this)}},title:{color:t=>t.chart.options.color,display:!1,position:"center",text:""}},descriptors:{_scriptable:t=>!t.startsWith("on"),labels:{_scriptable:t=>!["generateLabels","filter","sort"].includes(t)}}};class wa extends $s{constructor(t){super(),this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this._padding=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.width=void 0,this.height=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,e){const i=this.options;if(this.left=0,this.top=0,!i.display)return void(this.width=this.height=this.right=this.bottom=0);this.width=this.right=t,this.height=this.bottom=e;const s=n(i.text)?i.text.length:1;this._padding=ki(i.padding);const o=s*Si(i.font).lineHeight+this._padding.height;this.isHorizontal()?this.height=o:this.width=o}isHorizontal(){const t=this.options.position;return"top"===t||"bottom"===t}_drawArgs(t){const{top:e,left:i,bottom:s,right:n,options:o}=this,a=o.align;let r,l,h,c=0;return this.isHorizontal()?(l=ft(a,i,n),h=e+t,r=n-i):("left"===o.position?(l=i+t,h=ft(a,s,e),c=-.5*C):(l=n-t,h=ft(a,e,s),c=.5*C),r=s-e),{titleX:l,titleY:h,maxWidth:r,rotation:c}}draw(){const t=this.ctx,e=this.options;if(!e.display)return;const i=Si(e.font),s=i.lineHeight/2+this._padding.top,{titleX:n,titleY:o,maxWidth:a,rotation:r}=this._drawArgs(s);Ne(t,e.text,0,0,i,{color:e.color,maxWidth:a,rotation:r,textAlign:ut(e.align),textBaseline:"middle",translation:[n,o]})}}var ka={id:"title",_element:wa,start(t,e,i){!function(t,e){const i=new wa({ctx:t.ctx,options:e,chart:t});ls.configure(t,i,e),ls.addBox(t,i),t.titleBlock=i}(t,i)},stop(t){const e=t.titleBlock;ls.removeBox(t,e),delete t.titleBlock},beforeUpdate(t,e,i){const s=t.titleBlock;ls.configure(t,s,i),s.options=i},defaults:{align:"center",display:!1,font:{weight:"bold"},fullSize:!0,padding:10,position:"top",text:"",weight:2e3},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}};const Sa=new WeakMap;var Pa={id:"subtitle",start(t,e,i){const s=new wa({ctx:t.ctx,options:i,chart:t});ls.configure(t,s,i),ls.addBox(t,s),Sa.set(t,s)},stop(t){ls.removeBox(t,Sa.get(t)),Sa.delete(t)},beforeUpdate(t,e,i){const s=Sa.get(t);ls.configure(t,s,i),s.options=i},defaults:{align:"center",display:!1,font:{weight:"normal"},fullSize:!0,padding:0,position:"top",text:"",weight:1500},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}};const Da={average(t){if(!t.length)return!1;let e,i,s=new Set,n=0,o=0;for(e=0,i=t.length;et+e))/s.size,y:n/o}},nearest(t,e){if(!t.length)return!1;let i,s,n,o=e.x,a=e.y,r=Number.POSITIVE_INFINITY;for(i=0,s=t.length;i-1?t.split("\n"):t}function Aa(t,e){const{element:i,datasetIndex:s,index:n}=e,o=t.getDatasetMeta(s).controller,{label:a,value:r}=o.getLabelAndValue(n);return{chart:t,label:a,parsed:o.getParsed(n),raw:t.data.datasets[s].data[n],formattedValue:r,dataset:o.getDataset(),dataIndex:n,datasetIndex:s,element:i}}function Ta(t,e){const i=t.chart.ctx,{body:s,footer:n,title:o}=t,{boxWidth:a,boxHeight:r}=e,l=Si(e.bodyFont),h=Si(e.titleFont),c=Si(e.footerFont),d=o.length,f=n.length,g=s.length,p=ki(e.padding);let m=p.height,x=0,b=s.reduce(((t,e)=>t+e.before.length+e.lines.length+e.after.length),0);if(b+=t.beforeBody.length+t.afterBody.length,d&&(m+=d*h.lineHeight+(d-1)*e.titleSpacing+e.titleMarginBottom),b){m+=g*(e.displayColors?Math.max(r,l.lineHeight):l.lineHeight)+(b-g)*l.lineHeight+(b-1)*e.bodySpacing}f&&(m+=e.footerMarginTop+f*c.lineHeight+(f-1)*e.footerSpacing);let _=0;const y=function(t){x=Math.max(x,i.measureText(t).width+_)};return i.save(),i.font=h.string,u(t.title,y),i.font=l.string,u(t.beforeBody.concat(t.afterBody),y),_=e.displayColors?a+2+e.boxPadding:0,u(s,(t=>{u(t.before,y),u(t.lines,y),u(t.after,y)})),_=0,i.font=c.string,u(t.footer,y),i.restore(),x+=p.width,{width:x,height:m}}function La(t,e,i,s){const{x:n,width:o}=i,{width:a,chartArea:{left:r,right:l}}=t;let h="center";return"center"===s?h=n<=(r+l)/2?"left":"right":n<=o/2?h="left":n>=a-o/2&&(h="right"),function(t,e,i,s){const{x:n,width:o}=s,a=i.caretSize+i.caretPadding;return"left"===t&&n+o+a>e.width||"right"===t&&n-o-a<0||void 0}(h,t,e,i)&&(h="center"),h}function Ea(t,e,i){const s=i.yAlign||e.yAlign||function(t,e){const{y:i,height:s}=e;return it.height-s/2?"bottom":"center"}(t,i);return{xAlign:i.xAlign||e.xAlign||La(t,e,i,s),yAlign:s}}function Ra(t,e,i,s){const{caretSize:n,caretPadding:o,cornerRadius:a}=t,{xAlign:r,yAlign:l}=i,h=n+o,{topLeft:c,topRight:d,bottomLeft:u,bottomRight:f}=wi(a);let g=function(t,e){let{x:i,width:s}=t;return"right"===e?i-=s:"center"===e&&(i-=s/2),i}(e,r);const p=function(t,e,i){let{y:s,height:n}=t;return"top"===e?s+=i:s-="bottom"===e?n+i:n/2,s}(e,l,h);return"center"===l?"left"===r?g+=h:"right"===r&&(g-=h):"left"===r?g-=Math.max(c,u)+n:"right"===r&&(g+=Math.max(d,f)+n),{x:Z(g,0,s.width-e.width),y:Z(p,0,s.height-e.height)}}function Ia(t,e,i){const s=ki(i.padding);return"center"===e?t.x+t.width/2:"right"===e?t.x+t.width-s.right:t.x+s.left}function za(t){return Ca([],Oa(t))}function Fa(t,e){const i=e&&e.dataset&&e.dataset.tooltip&&e.dataset.tooltip.callbacks;return i?t.override(i):t}const Va={beforeTitle:e,title(t){if(t.length>0){const e=t[0],i=e.chart.data.labels,s=i?i.length:0;if(this&&this.options&&"dataset"===this.options.mode)return e.dataset.label||"";if(e.label)return e.label;if(s>0&&e.dataIndex{const e={before:[],lines:[],after:[]},n=Fa(i,t);Ca(e.before,Oa(Ba(n,"beforeLabel",this,t))),Ca(e.lines,Ba(n,"label",this,t)),Ca(e.after,Oa(Ba(n,"afterLabel",this,t))),s.push(e)})),s}getAfterBody(t,e){return za(Ba(e.callbacks,"afterBody",this,t))}getFooter(t,e){const{callbacks:i}=e,s=Ba(i,"beforeFooter",this,t),n=Ba(i,"footer",this,t),o=Ba(i,"afterFooter",this,t);let a=[];return a=Ca(a,Oa(s)),a=Ca(a,Oa(n)),a=Ca(a,Oa(o)),a}_createItems(t){const e=this._active,i=this.chart.data,s=[],n=[],o=[];let a,r,l=[];for(a=0,r=e.length;at.filter(e,s,n,i)))),t.itemSort&&(l=l.sort(((e,s)=>t.itemSort(e,s,i)))),u(l,(e=>{const i=Fa(t.callbacks,e);s.push(Ba(i,"labelColor",this,e)),n.push(Ba(i,"labelPointStyle",this,e)),o.push(Ba(i,"labelTextColor",this,e))})),this.labelColors=s,this.labelPointStyles=n,this.labelTextColors=o,this.dataPoints=l,l}update(t,e){const i=this.options.setContext(this.getContext()),s=this._active;let n,o=[];if(s.length){const t=Da[i.position].call(this,s,this._eventPosition);o=this._createItems(i),this.title=this.getTitle(o,i),this.beforeBody=this.getBeforeBody(o,i),this.body=this.getBody(o,i),this.afterBody=this.getAfterBody(o,i),this.footer=this.getFooter(o,i);const e=this._size=Ta(this,i),a=Object.assign({},t,e),r=Ea(this.chart,i,a),l=Ra(i,a,r,this.chart);this.xAlign=r.xAlign,this.yAlign=r.yAlign,n={opacity:1,x:l.x,y:l.y,width:e.width,height:e.height,caretX:t.x,caretY:t.y}}else 0!==this.opacity&&(n={opacity:0});this._tooltipItems=o,this.$context=void 0,n&&this._resolveAnimations().update(this,n),t&&i.external&&i.external.call(this,{chart:this.chart,tooltip:this,replay:e})}drawCaret(t,e,i,s){const n=this.getCaretPosition(t,i,s);e.lineTo(n.x1,n.y1),e.lineTo(n.x2,n.y2),e.lineTo(n.x3,n.y3)}getCaretPosition(t,e,i){const{xAlign:s,yAlign:n}=this,{caretSize:o,cornerRadius:a}=i,{topLeft:r,topRight:l,bottomLeft:h,bottomRight:c}=wi(a),{x:d,y:u}=t,{width:f,height:g}=e;let p,m,x,b,_,y;return"center"===n?(_=u+g/2,"left"===s?(p=d,m=p-o,b=_+o,y=_-o):(p=d+f,m=p+o,b=_-o,y=_+o),x=p):(m="left"===s?d+Math.max(r,h)+o:"right"===s?d+f-Math.max(l,c)-o:this.caretX,"top"===n?(b=u,_=b-o,p=m-o,x=m+o):(b=u+g,_=b+o,p=m+o,x=m-o),y=b),{x1:p,x2:m,x3:x,y1:b,y2:_,y3:y}}drawTitle(t,e,i){const s=this.title,n=s.length;let o,a,r;if(n){const l=Oi(i.rtl,this.x,this.width);for(t.x=Ia(this,i.titleAlign,i),e.textAlign=l.textAlign(i.titleAlign),e.textBaseline="middle",o=Si(i.titleFont),a=i.titleSpacing,e.fillStyle=i.titleColor,e.font=o.string,r=0;r0!==t))?(t.beginPath(),t.fillStyle=n.multiKeyBackground,He(t,{x:e,y:g,w:h,h:l,radius:r}),t.fill(),t.stroke(),t.fillStyle=a.backgroundColor,t.beginPath(),He(t,{x:i,y:g+1,w:h-2,h:l-2,radius:r}),t.fill()):(t.fillStyle=n.multiKeyBackground,t.fillRect(e,g,h,l),t.strokeRect(e,g,h,l),t.fillStyle=a.backgroundColor,t.fillRect(i,g+1,h-2,l-2))}t.fillStyle=this.labelTextColors[i]}drawBody(t,e,i){const{body:s}=this,{bodySpacing:n,bodyAlign:o,displayColors:a,boxHeight:r,boxWidth:l,boxPadding:h}=i,c=Si(i.bodyFont);let d=c.lineHeight,f=0;const g=Oi(i.rtl,this.x,this.width),p=function(i){e.fillText(i,g.x(t.x+f),t.y+d/2),t.y+=d+n},m=g.textAlign(o);let x,b,_,y,v,M,w;for(e.textAlign=o,e.textBaseline="middle",e.font=c.string,t.x=Ia(this,m,i),e.fillStyle=i.bodyColor,u(this.beforeBody,p),f=a&&"right"!==m?"center"===o?l/2+h:l+2+h:0,y=0,M=s.length;y0&&e.stroke()}_updateAnimationTarget(t){const e=this.chart,i=this.$animations,s=i&&i.x,n=i&&i.y;if(s||n){const i=Da[t.position].call(this,this._active,this._eventPosition);if(!i)return;const o=this._size=Ta(this,t),a=Object.assign({},i,this._size),r=Ea(e,t,a),l=Ra(t,a,r,e);s._to===l.x&&n._to===l.y||(this.xAlign=r.xAlign,this.yAlign=r.yAlign,this.width=o.width,this.height=o.height,this.caretX=i.x,this.caretY=i.y,this._resolveAnimations().update(this,l))}}_willRender(){return!!this.opacity}draw(t){const e=this.options.setContext(this.getContext());let i=this.opacity;if(!i)return;this._updateAnimationTarget(e);const s={width:this.width,height:this.height},n={x:this.x,y:this.y};i=Math.abs(i)<.001?0:i;const o=ki(e.padding),a=this.title.length||this.beforeBody.length||this.body.length||this.afterBody.length||this.footer.length;e.enabled&&a&&(t.save(),t.globalAlpha=i,this.drawBackground(n,t,s,e),Ai(t,e.textDirection),n.y+=o.top,this.drawTitle(n,t,e),this.drawBody(n,t,e),this.drawFooter(n,t,e),Ti(t,e.textDirection),t.restore())}getActiveElements(){return this._active||[]}setActiveElements(t,e){const i=this._active,s=t.map((({datasetIndex:t,index:e})=>{const i=this.chart.getDatasetMeta(t);if(!i)throw new Error("Cannot find a dataset at index "+t);return{datasetIndex:t,element:i.data[e],index:e}})),n=!f(i,s),o=this._positionChanged(s,e);(n||o)&&(this._active=s,this._eventPosition=e,this._ignoreReplayEvents=!0,this.update(!0))}handleEvent(t,e,i=!0){if(e&&this._ignoreReplayEvents)return!1;this._ignoreReplayEvents=!1;const s=this.options,n=this._active||[],o=this._getActiveElements(t,n,e,i),a=this._positionChanged(o,t),r=e||!f(o,n)||a;return r&&(this._active=o,(s.enabled||s.external)&&(this._eventPosition={x:t.x,y:t.y},this.update(!0,e))),r}_getActiveElements(t,e,i,s){const n=this.options;if("mouseout"===t.type)return[];if(!s)return e.filter((t=>this.chart.data.datasets[t.datasetIndex]&&void 0!==this.chart.getDatasetMeta(t.datasetIndex).controller.getParsed(t.index)));const o=this.chart.getElementsAtEventForMode(t,n.mode,n,i);return n.reverse&&o.reverse(),o}_positionChanged(t,e){const{caretX:i,caretY:s,options:n}=this,o=Da[n.position].call(this,t,e);return!1!==o&&(i!==o.x||s!==o.y)}}var Na={id:"tooltip",_element:Wa,positioners:Da,afterInit(t,e,i){i&&(t.tooltip=new Wa({chart:t,options:i}))},beforeUpdate(t,e,i){t.tooltip&&t.tooltip.initialize(i)},reset(t,e,i){t.tooltip&&t.tooltip.initialize(i)},afterDraw(t){const e=t.tooltip;if(e&&e._willRender()){const i={tooltip:e};if(!1===t.notifyPlugins("beforeTooltipDraw",{...i,cancelable:!0}))return;e.draw(t.ctx),t.notifyPlugins("afterTooltipDraw",i)}},afterEvent(t,e){if(t.tooltip){const i=e.replay;t.tooltip.handleEvent(e.event,i,e.inChartArea)&&(e.changed=!0)}},defaults:{enabled:!0,external:null,position:"average",backgroundColor:"rgba(0,0,0,0.8)",titleColor:"#fff",titleFont:{weight:"bold"},titleSpacing:2,titleMarginBottom:6,titleAlign:"left",bodyColor:"#fff",bodySpacing:2,bodyFont:{},bodyAlign:"left",footerColor:"#fff",footerSpacing:2,footerMarginTop:6,footerFont:{weight:"bold"},footerAlign:"left",padding:6,caretPadding:2,caretSize:5,cornerRadius:6,boxHeight:(t,e)=>e.bodyFont.size,boxWidth:(t,e)=>e.bodyFont.size,multiKeyBackground:"#fff",displayColors:!0,boxPadding:0,borderColor:"rgba(0,0,0,0)",borderWidth:0,animation:{duration:400,easing:"easeOutQuart"},animations:{numbers:{type:"number",properties:["x","y","width","height","caretX","caretY"]},opacity:{easing:"linear",duration:200}},callbacks:Va},defaultRoutes:{bodyFont:"font",footerFont:"font",titleFont:"font"},descriptors:{_scriptable:t=>"filter"!==t&&"itemSort"!==t&&"external"!==t,_indexable:!1,callbacks:{_scriptable:!1,_indexable:!1},animation:{_fallback:!1},animations:{_fallback:"animation"}},additionalOptionScopes:["interaction"]};return Tn.register(Un,$o,go,t),Tn.helpers={...Hi},Tn._adapters=In,Tn.Animation=As,Tn.Animations=Ts,Tn.animator=bt,Tn.controllers=nn.controllers.items,Tn.DatasetController=js,Tn.Element=$s,Tn.elements=go,Tn.Interaction=Ki,Tn.layouts=ls,Tn.platforms=Ds,Tn.Scale=tn,Tn.Ticks=ae,Object.assign(Tn,Un,$o,go,t,Ds),Tn.Chart=Tn,"undefined"!=typeof window&&(window.Chart=Tn),Tn})); -//# sourceMappingURL=chart.umd.min.js.map diff --git a/web_test/PLC S7-315 Streamer & Logger_files/chartjs-adapter-date-fns b/web_test/PLC S7-315 Streamer & Logger_files/chartjs-adapter-date-fns deleted file mode 100644 index 37bffe6..0000000 --- a/web_test/PLC S7-315 Streamer & Logger_files/chartjs-adapter-date-fns +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * chartjs-adapter-date-fns v3.0.0 - * https://www.chartjs.org - * (c) 2022 chartjs-adapter-date-fns Contributors - * Released under the MIT license - */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(require("chart.js")):"function"==typeof define&&define.amd?define(["chart.js"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).Chart)}(this,(function(t){"use strict";function e(t){if(null===t||!0===t||!1===t)return NaN;var e=Number(t);return isNaN(e)?e:e<0?Math.ceil(e):Math.floor(e)}function r(t,e){if(e.length1?"s":"")+" required, but only "+e.length+" present")}function n(t){r(1,arguments);var e=Object.prototype.toString.call(t);return t instanceof Date||"object"==typeof t&&"[object Date]"===e?new Date(t.getTime()):"number"==typeof t||"[object Number]"===e?new Date(t):("string"!=typeof t&&"[object String]"!==e||"undefined"==typeof console||(console.warn("Starting with v2.0.0-beta.1 date-fns doesn't accept strings as date arguments. Please use `parseISO` to parse strings. See: https://git.io/fjule"),console.warn((new Error).stack)),new Date(NaN))}function a(t,a){r(2,arguments);var i=n(t),o=e(a);return isNaN(o)?new Date(NaN):o?(i.setDate(i.getDate()+o),i):i}function i(t,a){r(2,arguments);var i=n(t),o=e(a);if(isNaN(o))return new Date(NaN);if(!o)return i;var u=i.getDate(),s=new Date(i.getTime());s.setMonth(i.getMonth()+o+1,0);var c=s.getDate();return u>=c?s:(i.setFullYear(s.getFullYear(),s.getMonth(),u),i)}function o(t,a){r(2,arguments);var i=n(t).getTime(),o=e(a);return new Date(i+o)}var u=36e5;function s(t,a){r(1,arguments);var i=a||{},o=i.locale,u=o&&o.options&&o.options.weekStartsOn,s=null==u?0:e(u),c=null==i.weekStartsOn?s:e(i.weekStartsOn);if(!(c>=0&&c<=6))throw new RangeError("weekStartsOn must be between 0 and 6 inclusively");var d=n(t),l=d.getDay(),f=(l0?1:o}function m(t){r(1,arguments);var e=n(t);return!isNaN(e)}function w(t,e){r(2,arguments);var a=n(t),i=n(e),o=a.getFullYear()-i.getFullYear(),u=a.getMonth()-i.getMonth();return 12*o+u}function g(t,e){r(2,arguments);var a=n(t),i=n(e);return a.getFullYear()-i.getFullYear()}function v(t,e){var r=t.getFullYear()-e.getFullYear()||t.getMonth()-e.getMonth()||t.getDate()-e.getDate()||t.getHours()-e.getHours()||t.getMinutes()-e.getMinutes()||t.getSeconds()-e.getSeconds()||t.getMilliseconds()-e.getMilliseconds();return r<0?-1:r>0?1:r}function y(t,e){r(2,arguments);var a=n(t),i=n(e),o=v(a,i),u=Math.abs(f(a,i));a.setDate(a.getDate()-o*u);var s=v(a,i)===-o,c=o*(u-s);return 0===c?0:c}function b(t,e){r(2,arguments);var a=n(t),i=n(e);return a.getTime()-i.getTime()}var T=36e5;function p(t){r(1,arguments);var e=n(t);return e.setHours(23,59,59,999),e}function C(t){r(1,arguments);var e=n(t),a=e.getMonth();return e.setFullYear(e.getFullYear(),a+1,0),e.setHours(23,59,59,999),e}function M(t){r(1,arguments);var e=n(t);return p(e).getTime()===C(e).getTime()}function D(t,e){r(2,arguments);var a,i=n(t),o=n(e),u=h(i,o),s=Math.abs(w(i,o));if(s<1)a=0;else{1===i.getMonth()&&i.getDate()>27&&i.setDate(30),i.setMonth(i.getMonth()-u*s);var c=h(i,o)===-u;M(n(t))&&1===s&&1===h(t,o)&&(c=!1),a=u*(s-c)}return 0===a?0:a}var x={lessThanXSeconds:{one:"less than a second",other:"less than {{count}} seconds"},xSeconds:{one:"1 second",other:"{{count}} seconds"},halfAMinute:"half a minute",lessThanXMinutes:{one:"less than a minute",other:"less than {{count}} minutes"},xMinutes:{one:"1 minute",other:"{{count}} minutes"},aboutXHours:{one:"about 1 hour",other:"about {{count}} hours"},xHours:{one:"1 hour",other:"{{count}} hours"},xDays:{one:"1 day",other:"{{count}} days"},aboutXWeeks:{one:"about 1 week",other:"about {{count}} weeks"},xWeeks:{one:"1 week",other:"{{count}} weeks"},aboutXMonths:{one:"about 1 month",other:"about {{count}} months"},xMonths:{one:"1 month",other:"{{count}} months"},aboutXYears:{one:"about 1 year",other:"about {{count}} years"},xYears:{one:"1 year",other:"{{count}} years"},overXYears:{one:"over 1 year",other:"over {{count}} years"},almostXYears:{one:"almost 1 year",other:"almost {{count}} years"}};function k(t){return function(e){var r=e||{},n=r.width?String(r.width):t.defaultWidth;return t.formats[n]||t.formats[t.defaultWidth]}}var U={date:k({formats:{full:"EEEE, MMMM do, y",long:"MMMM do, y",medium:"MMM d, y",short:"MM/dd/yyyy"},defaultWidth:"full"}),time:k({formats:{full:"h:mm:ss a zzzz",long:"h:mm:ss a z",medium:"h:mm:ss a",short:"h:mm a"},defaultWidth:"full"}),dateTime:k({formats:{full:"{{date}} 'at' {{time}}",long:"{{date}} 'at' {{time}}",medium:"{{date}}, {{time}}",short:"{{date}}, {{time}}"},defaultWidth:"full"})},Y={lastWeek:"'last' eeee 'at' p",yesterday:"'yesterday at' p",today:"'today at' p",tomorrow:"'tomorrow at' p",nextWeek:"eeee 'at' p",other:"P"};function N(t){return function(e,r){var n,a=r||{};if("formatting"===(a.context?String(a.context):"standalone")&&t.formattingValues){var i=t.defaultFormattingWidth||t.defaultWidth,o=a.width?String(a.width):i;n=t.formattingValues[o]||t.formattingValues[i]}else{var u=t.defaultWidth,s=a.width?String(a.width):t.defaultWidth;n=t.values[s]||t.values[u]}return n[t.argumentCallback?t.argumentCallback(e):e]}}function S(t){return function(e,r){var n=String(e),a=r||{},i=a.width,o=i&&t.matchPatterns[i]||t.matchPatterns[t.defaultMatchWidth],u=n.match(o);if(!u)return null;var s,c=u[0],d=i&&t.parsePatterns[i]||t.parsePatterns[t.defaultParseWidth];return s="[object Array]"===Object.prototype.toString.call(d)?function(t,e){for(var r=0;r0?"in "+n:n+" ago":n},formatLong:U,formatRelative:function(t,e,r,n){return Y[t]},localize:{ordinalNumber:function(t,e){var r=Number(t),n=r%100;if(n>20||n<10)switch(n%10){case 1:return r+"st";case 2:return r+"nd";case 3:return r+"rd"}return r+"th"},era:N({values:{narrow:["B","A"],abbreviated:["BC","AD"],wide:["Before Christ","Anno Domini"]},defaultWidth:"wide"}),quarter:N({values:{narrow:["1","2","3","4"],abbreviated:["Q1","Q2","Q3","Q4"],wide:["1st quarter","2nd quarter","3rd quarter","4th quarter"]},defaultWidth:"wide",argumentCallback:function(t){return Number(t)-1}}),month:N({values:{narrow:["J","F","M","A","M","J","J","A","S","O","N","D"],abbreviated:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],wide:["January","February","March","April","May","June","July","August","September","October","November","December"]},defaultWidth:"wide"}),day:N({values:{narrow:["S","M","T","W","T","F","S"],short:["Su","Mo","Tu","We","Th","Fr","Sa"],abbreviated:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],wide:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"]},defaultWidth:"wide"}),dayPeriod:N({values:{narrow:{am:"a",pm:"p",midnight:"mi",noon:"n",morning:"morning",afternoon:"afternoon",evening:"evening",night:"night"},abbreviated:{am:"AM",pm:"PM",midnight:"midnight",noon:"noon",morning:"morning",afternoon:"afternoon",evening:"evening",night:"night"},wide:{am:"a.m.",pm:"p.m.",midnight:"midnight",noon:"noon",morning:"morning",afternoon:"afternoon",evening:"evening",night:"night"}},defaultWidth:"wide",formattingValues:{narrow:{am:"a",pm:"p",midnight:"mi",noon:"n",morning:"in the morning",afternoon:"in the afternoon",evening:"in the evening",night:"at night"},abbreviated:{am:"AM",pm:"PM",midnight:"midnight",noon:"noon",morning:"in the morning",afternoon:"in the afternoon",evening:"in the evening",night:"at night"},wide:{am:"a.m.",pm:"p.m.",midnight:"midnight",noon:"noon",morning:"in the morning",afternoon:"in the afternoon",evening:"in the evening",night:"at night"}},defaultFormattingWidth:"wide"})},match:{ordinalNumber:(P={matchPattern:/^(\d+)(th|st|nd|rd)?/i,parsePattern:/\d+/i,valueCallback:function(t){return parseInt(t,10)}},function(t,e){var r=String(t),n=e||{},a=r.match(P.matchPattern);if(!a)return null;var i=a[0],o=r.match(P.parsePattern);if(!o)return null;var u=P.valueCallback?P.valueCallback(o[0]):o[0];return{value:u=n.valueCallback?n.valueCallback(u):u,rest:r.slice(i.length)}}),era:S({matchPatterns:{narrow:/^(b|a)/i,abbreviated:/^(b\.?\s?c\.?|b\.?\s?c\.?\s?e\.?|a\.?\s?d\.?|c\.?\s?e\.?)/i,wide:/^(before christ|before common era|anno domini|common era)/i},defaultMatchWidth:"wide",parsePatterns:{any:[/^b/i,/^(a|c)/i]},defaultParseWidth:"any"}),quarter:S({matchPatterns:{narrow:/^[1234]/i,abbreviated:/^q[1234]/i,wide:/^[1234](th|st|nd|rd)? quarter/i},defaultMatchWidth:"wide",parsePatterns:{any:[/1/i,/2/i,/3/i,/4/i]},defaultParseWidth:"any",valueCallback:function(t){return t+1}}),month:S({matchPatterns:{narrow:/^[jfmasond]/i,abbreviated:/^(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)/i,wide:/^(january|february|march|april|may|june|july|august|september|october|november|december)/i},defaultMatchWidth:"wide",parsePatterns:{narrow:[/^j/i,/^f/i,/^m/i,/^a/i,/^m/i,/^j/i,/^j/i,/^a/i,/^s/i,/^o/i,/^n/i,/^d/i],any:[/^ja/i,/^f/i,/^mar/i,/^ap/i,/^may/i,/^jun/i,/^jul/i,/^au/i,/^s/i,/^o/i,/^n/i,/^d/i]},defaultParseWidth:"any"}),day:S({matchPatterns:{narrow:/^[smtwf]/i,short:/^(su|mo|tu|we|th|fr|sa)/i,abbreviated:/^(sun|mon|tue|wed|thu|fri|sat)/i,wide:/^(sunday|monday|tuesday|wednesday|thursday|friday|saturday)/i},defaultMatchWidth:"wide",parsePatterns:{narrow:[/^s/i,/^m/i,/^t/i,/^w/i,/^t/i,/^f/i,/^s/i],any:[/^su/i,/^m/i,/^tu/i,/^w/i,/^th/i,/^f/i,/^sa/i]},defaultParseWidth:"any"}),dayPeriod:S({matchPatterns:{narrow:/^(a|p|mi|n|(in the|at) (morning|afternoon|evening|night))/i,any:/^([ap]\.?\s?m\.?|midnight|noon|(in the|at) (morning|afternoon|evening|night))/i},defaultMatchWidth:"any",parsePatterns:{any:{am:/^a/i,pm:/^p/i,midnight:/^mi/i,noon:/^no/i,morning:/morning/i,afternoon:/afternoon/i,evening:/evening/i,night:/night/i}},defaultParseWidth:"any"})},options:{weekStartsOn:0,firstWeekContainsDate:1}};function H(t,n){r(2,arguments);var a=e(n);return o(t,-a)}function E(t,e){for(var r=t<0?"-":"",n=Math.abs(t).toString();n.length0?r:1-r;return E("yy"===e?n%100:n,e.length)},M:function(t,e){var r=t.getUTCMonth();return"M"===e?String(r+1):E(r+1,2)},d:function(t,e){return E(t.getUTCDate(),e.length)},a:function(t,e){var r=t.getUTCHours()/12>=1?"pm":"am";switch(e){case"a":case"aa":return r.toUpperCase();case"aaa":return r;case"aaaaa":return r[0];default:return"am"===r?"a.m.":"p.m."}},h:function(t,e){return E(t.getUTCHours()%12||12,e.length)},H:function(t,e){return E(t.getUTCHours(),e.length)},m:function(t,e){return E(t.getUTCMinutes(),e.length)},s:function(t,e){return E(t.getUTCSeconds(),e.length)},S:function(t,e){var r=e.length,n=t.getUTCMilliseconds();return E(Math.floor(n*Math.pow(10,r-3)),e.length)}},F=864e5;function W(t){r(1,arguments);var e=1,a=n(t),i=a.getUTCDay(),o=(i=o.getTime()?a+1:e.getTime()>=s.getTime()?a:a-1}function Q(t){r(1,arguments);var e=L(t),n=new Date(0);n.setUTCFullYear(e,0,4),n.setUTCHours(0,0,0,0);var a=W(n);return a}var R=6048e5;function I(t){r(1,arguments);var e=n(t),a=W(e).getTime()-Q(e).getTime();return Math.round(a/R)+1}function G(t,a){r(1,arguments);var i=a||{},o=i.locale,u=o&&o.options&&o.options.weekStartsOn,s=null==u?0:e(u),c=null==i.weekStartsOn?s:e(i.weekStartsOn);if(!(c>=0&&c<=6))throw new RangeError("weekStartsOn must be between 0 and 6 inclusively");var d=n(t),l=d.getUTCDay(),f=(l=1&&l<=7))throw new RangeError("firstWeekContainsDate must be between 1 and 7 inclusively");var f=new Date(0);f.setUTCFullYear(o+1,0,l),f.setUTCHours(0,0,0,0);var h=G(f,a),m=new Date(0);m.setUTCFullYear(o,0,l),m.setUTCHours(0,0,0,0);var w=G(m,a);return i.getTime()>=h.getTime()?o+1:i.getTime()>=w.getTime()?o:o-1}function j(t,n){r(1,arguments);var a=n||{},i=a.locale,o=i&&i.options&&i.options.firstWeekContainsDate,u=null==o?1:e(o),s=null==a.firstWeekContainsDate?u:e(a.firstWeekContainsDate),c=X(t,n),d=new Date(0);d.setUTCFullYear(c,0,s),d.setUTCHours(0,0,0,0);var l=G(d,n);return l}var B=6048e5;function z(t,e){r(1,arguments);var a=n(t),i=G(a,e).getTime()-j(a,e).getTime();return Math.round(i/B)+1}var A="midnight",Z="noon",K="morning",$="afternoon",_="evening",J="night",V={G:function(t,e,r){var n=t.getUTCFullYear()>0?1:0;switch(e){case"G":case"GG":case"GGG":return r.era(n,{width:"abbreviated"});case"GGGGG":return r.era(n,{width:"narrow"});default:return r.era(n,{width:"wide"})}},y:function(t,e,r){if("yo"===e){var n=t.getUTCFullYear(),a=n>0?n:1-n;return r.ordinalNumber(a,{unit:"year"})}return O.y(t,e)},Y:function(t,e,r,n){var a=X(t,n),i=a>0?a:1-a;return"YY"===e?E(i%100,2):"Yo"===e?r.ordinalNumber(i,{unit:"year"}):E(i,e.length)},R:function(t,e){return E(L(t),e.length)},u:function(t,e){return E(t.getUTCFullYear(),e.length)},Q:function(t,e,r){var n=Math.ceil((t.getUTCMonth()+1)/3);switch(e){case"Q":return String(n);case"QQ":return E(n,2);case"Qo":return r.ordinalNumber(n,{unit:"quarter"});case"QQQ":return r.quarter(n,{width:"abbreviated",context:"formatting"});case"QQQQQ":return r.quarter(n,{width:"narrow",context:"formatting"});default:return r.quarter(n,{width:"wide",context:"formatting"})}},q:function(t,e,r){var n=Math.ceil((t.getUTCMonth()+1)/3);switch(e){case"q":return String(n);case"qq":return E(n,2);case"qo":return r.ordinalNumber(n,{unit:"quarter"});case"qqq":return r.quarter(n,{width:"abbreviated",context:"standalone"});case"qqqqq":return r.quarter(n,{width:"narrow",context:"standalone"});default:return r.quarter(n,{width:"wide",context:"standalone"})}},M:function(t,e,r){var n=t.getUTCMonth();switch(e){case"M":case"MM":return O.M(t,e);case"Mo":return r.ordinalNumber(n+1,{unit:"month"});case"MMM":return r.month(n,{width:"abbreviated",context:"formatting"});case"MMMMM":return r.month(n,{width:"narrow",context:"formatting"});default:return r.month(n,{width:"wide",context:"formatting"})}},L:function(t,e,r){var n=t.getUTCMonth();switch(e){case"L":return String(n+1);case"LL":return E(n+1,2);case"Lo":return r.ordinalNumber(n+1,{unit:"month"});case"LLL":return r.month(n,{width:"abbreviated",context:"standalone"});case"LLLLL":return r.month(n,{width:"narrow",context:"standalone"});default:return r.month(n,{width:"wide",context:"standalone"})}},w:function(t,e,r,n){var a=z(t,n);return"wo"===e?r.ordinalNumber(a,{unit:"week"}):E(a,e.length)},I:function(t,e,r){var n=I(t);return"Io"===e?r.ordinalNumber(n,{unit:"week"}):E(n,e.length)},d:function(t,e,r){return"do"===e?r.ordinalNumber(t.getUTCDate(),{unit:"date"}):O.d(t,e)},D:function(t,e,a){var i=function(t){r(1,arguments);var e=n(t),a=e.getTime();e.setUTCMonth(0,1),e.setUTCHours(0,0,0,0);var i=e.getTime(),o=a-i;return Math.floor(o/F)+1}(t);return"Do"===e?a.ordinalNumber(i,{unit:"dayOfYear"}):E(i,e.length)},E:function(t,e,r){var n=t.getUTCDay();switch(e){case"E":case"EE":case"EEE":return r.day(n,{width:"abbreviated",context:"formatting"});case"EEEEE":return r.day(n,{width:"narrow",context:"formatting"});case"EEEEEE":return r.day(n,{width:"short",context:"formatting"});default:return r.day(n,{width:"wide",context:"formatting"})}},e:function(t,e,r,n){var a=t.getUTCDay(),i=(a-n.weekStartsOn+8)%7||7;switch(e){case"e":return String(i);case"ee":return E(i,2);case"eo":return r.ordinalNumber(i,{unit:"day"});case"eee":return r.day(a,{width:"abbreviated",context:"formatting"});case"eeeee":return r.day(a,{width:"narrow",context:"formatting"});case"eeeeee":return r.day(a,{width:"short",context:"formatting"});default:return r.day(a,{width:"wide",context:"formatting"})}},c:function(t,e,r,n){var a=t.getUTCDay(),i=(a-n.weekStartsOn+8)%7||7;switch(e){case"c":return String(i);case"cc":return E(i,e.length);case"co":return r.ordinalNumber(i,{unit:"day"});case"ccc":return r.day(a,{width:"abbreviated",context:"standalone"});case"ccccc":return r.day(a,{width:"narrow",context:"standalone"});case"cccccc":return r.day(a,{width:"short",context:"standalone"});default:return r.day(a,{width:"wide",context:"standalone"})}},i:function(t,e,r){var n=t.getUTCDay(),a=0===n?7:n;switch(e){case"i":return String(a);case"ii":return E(a,e.length);case"io":return r.ordinalNumber(a,{unit:"day"});case"iii":return r.day(n,{width:"abbreviated",context:"formatting"});case"iiiii":return r.day(n,{width:"narrow",context:"formatting"});case"iiiiii":return r.day(n,{width:"short",context:"formatting"});default:return r.day(n,{width:"wide",context:"formatting"})}},a:function(t,e,r){var n=t.getUTCHours()/12>=1?"pm":"am";switch(e){case"a":case"aa":return r.dayPeriod(n,{width:"abbreviated",context:"formatting"});case"aaa":return r.dayPeriod(n,{width:"abbreviated",context:"formatting"}).toLowerCase();case"aaaaa":return r.dayPeriod(n,{width:"narrow",context:"formatting"});default:return r.dayPeriod(n,{width:"wide",context:"formatting"})}},b:function(t,e,r){var n,a=t.getUTCHours();switch(n=12===a?Z:0===a?A:a/12>=1?"pm":"am",e){case"b":case"bb":return r.dayPeriod(n,{width:"abbreviated",context:"formatting"});case"bbb":return r.dayPeriod(n,{width:"abbreviated",context:"formatting"}).toLowerCase();case"bbbbb":return r.dayPeriod(n,{width:"narrow",context:"formatting"});default:return r.dayPeriod(n,{width:"wide",context:"formatting"})}},B:function(t,e,r){var n,a=t.getUTCHours();switch(n=a>=17?_:a>=12?$:a>=4?K:J,e){case"B":case"BB":case"BBB":return r.dayPeriod(n,{width:"abbreviated",context:"formatting"});case"BBBBB":return r.dayPeriod(n,{width:"narrow",context:"formatting"});default:return r.dayPeriod(n,{width:"wide",context:"formatting"})}},h:function(t,e,r){if("ho"===e){var n=t.getUTCHours()%12;return 0===n&&(n=12),r.ordinalNumber(n,{unit:"hour"})}return O.h(t,e)},H:function(t,e,r){return"Ho"===e?r.ordinalNumber(t.getUTCHours(),{unit:"hour"}):O.H(t,e)},K:function(t,e,r){var n=t.getUTCHours()%12;return"Ko"===e?r.ordinalNumber(n,{unit:"hour"}):E(n,e.length)},k:function(t,e,r){var n=t.getUTCHours();return 0===n&&(n=24),"ko"===e?r.ordinalNumber(n,{unit:"hour"}):E(n,e.length)},m:function(t,e,r){return"mo"===e?r.ordinalNumber(t.getUTCMinutes(),{unit:"minute"}):O.m(t,e)},s:function(t,e,r){return"so"===e?r.ordinalNumber(t.getUTCSeconds(),{unit:"second"}):O.s(t,e)},S:function(t,e){return O.S(t,e)},X:function(t,e,r,n){var a=(n._originalDate||t).getTimezoneOffset();if(0===a)return"Z";switch(e){case"X":return et(a);case"XXXX":case"XX":return rt(a);default:return rt(a,":")}},x:function(t,e,r,n){var a=(n._originalDate||t).getTimezoneOffset();switch(e){case"x":return et(a);case"xxxx":case"xx":return rt(a);default:return rt(a,":")}},O:function(t,e,r,n){var a=(n._originalDate||t).getTimezoneOffset();switch(e){case"O":case"OO":case"OOO":return"GMT"+tt(a,":");default:return"GMT"+rt(a,":")}},z:function(t,e,r,n){var a=(n._originalDate||t).getTimezoneOffset();switch(e){case"z":case"zz":case"zzz":return"GMT"+tt(a,":");default:return"GMT"+rt(a,":")}},t:function(t,e,r,n){var a=n._originalDate||t;return E(Math.floor(a.getTime()/1e3),e.length)},T:function(t,e,r,n){return E((n._originalDate||t).getTime(),e.length)}};function tt(t,e){var r=t>0?"-":"+",n=Math.abs(t),a=Math.floor(n/60),i=n%60;if(0===i)return r+String(a);var o=e||"";return r+String(a)+o+E(i,2)}function et(t,e){return t%60==0?(t>0?"-":"+")+E(Math.abs(t)/60,2):rt(t,e)}function rt(t,e){var r=e||"",n=t>0?"-":"+",a=Math.abs(t);return n+E(Math.floor(a/60),2)+r+E(a%60,2)}var nt=V;function at(t,e){switch(t){case"P":return e.date({width:"short"});case"PP":return e.date({width:"medium"});case"PPP":return e.date({width:"long"});default:return e.date({width:"full"})}}function it(t,e){switch(t){case"p":return e.time({width:"short"});case"pp":return e.time({width:"medium"});case"ppp":return e.time({width:"long"});default:return e.time({width:"full"})}}var ot={p:it,P:function(t,e){var r,n=t.match(/(P+)(p+)?/),a=n[1],i=n[2];if(!i)return at(t,e);switch(a){case"P":r=e.dateTime({width:"short"});break;case"PP":r=e.dateTime({width:"medium"});break;case"PPP":r=e.dateTime({width:"long"});break;default:r=e.dateTime({width:"full"})}return r.replace("{{date}}",at(a,e)).replace("{{time}}",it(i,e))}},ut=ot,st=["D","DD"],ct=["YY","YYYY"];function dt(t){return-1!==st.indexOf(t)}function lt(t){return-1!==ct.indexOf(t)}function ft(t,e,r){if("YYYY"===t)throw new RangeError("Use `yyyy` instead of `YYYY` (in `".concat(e,"`) for formatting years to the input `").concat(r,"`; see: https://git.io/fxCyr"));if("YY"===t)throw new RangeError("Use `yy` instead of `YY` (in `".concat(e,"`) for formatting years to the input `").concat(r,"`; see: https://git.io/fxCyr"));if("D"===t)throw new RangeError("Use `d` instead of `D` (in `".concat(e,"`) for formatting days of the month to the input `").concat(r,"`; see: https://git.io/fxCyr"));if("DD"===t)throw new RangeError("Use `dd` instead of `DD` (in `".concat(e,"`) for formatting days of the month to the input `").concat(r,"`; see: https://git.io/fxCyr"))}var ht=/[yYQqMLwIdDecihHKkms]o|(\w)\1*|''|'(''|[^'])+('|$)|./g,mt=/P+p+|P+|p+|''|'(''|[^'])+('|$)|./g,wt=/^'([^]*?)'?$/,gt=/''/g,vt=/[a-zA-Z]/;function yt(t){return t.match(wt)[1].replace(gt,"'")}function bt(t,e){if(null==t)throw new TypeError("assign requires that input parameter not be null or undefined");for(var r in e=e||{})e.hasOwnProperty(r)&&(t[r]=e[r]);return t}function Tt(t,a,i){r(2,arguments);var o=i||{},u=o.locale,s=u&&u.options&&u.options.weekStartsOn,c=null==s?0:e(s),d=null==o.weekStartsOn?c:e(o.weekStartsOn);if(!(d>=0&&d<=6))throw new RangeError("weekStartsOn must be between 0 and 6 inclusively");var l=n(t),f=e(a),h=l.getUTCDay(),m=f%7,w=(m+7)%7,g=(w0,a=n?e:1-e;if(a<=50)r=t||100;else{var i=a+50;r=t+100*Math.floor(i/100)-(t>=i%100?100:0)}return n?r:1-r}var Jt=[31,28,31,30,31,30,31,31,30,31,30,31],Vt=[31,29,31,30,31,30,31,31,30,31,30,31];function te(t){return t%400==0||t%4==0&&t%100!=0}var ee={G:{priority:140,parse:function(t,e,r,n){switch(e){case"G":case"GG":case"GGG":return r.era(t,{width:"abbreviated"})||r.era(t,{width:"narrow"});case"GGGGG":return r.era(t,{width:"narrow"});default:return r.era(t,{width:"wide"})||r.era(t,{width:"abbreviated"})||r.era(t,{width:"narrow"})}},set:function(t,e,r,n){return e.era=r,t.setUTCFullYear(r,0,1),t.setUTCHours(0,0,0,0),t},incompatibleTokens:["R","u","t","T"]},y:{priority:130,parse:function(t,e,r,n){var a=function(t){return{year:t,isTwoDigitYear:"yy"===e}};switch(e){case"y":return Zt(4,t,a);case"yo":return r.ordinalNumber(t,{unit:"year",valueCallback:a});default:return Zt(e.length,t,a)}},validate:function(t,e,r){return e.isTwoDigitYear||e.year>0},set:function(t,e,r,n){var a=t.getUTCFullYear();if(r.isTwoDigitYear){var i=_t(r.year,a);return t.setUTCFullYear(i,0,1),t.setUTCHours(0,0,0,0),t}var o="era"in e&&1!==e.era?1-r.year:r.year;return t.setUTCFullYear(o,0,1),t.setUTCHours(0,0,0,0),t},incompatibleTokens:["Y","R","u","w","I","i","e","c","t","T"]},Y:{priority:130,parse:function(t,e,r,n){var a=function(t){return{year:t,isTwoDigitYear:"YY"===e}};switch(e){case"Y":return Zt(4,t,a);case"Yo":return r.ordinalNumber(t,{unit:"year",valueCallback:a});default:return Zt(e.length,t,a)}},validate:function(t,e,r){return e.isTwoDigitYear||e.year>0},set:function(t,e,r,n){var a=X(t,n);if(r.isTwoDigitYear){var i=_t(r.year,a);return t.setUTCFullYear(i,0,n.firstWeekContainsDate),t.setUTCHours(0,0,0,0),G(t,n)}var o="era"in e&&1!==e.era?1-r.year:r.year;return t.setUTCFullYear(o,0,n.firstWeekContainsDate),t.setUTCHours(0,0,0,0),G(t,n)},incompatibleTokens:["y","R","u","Q","q","M","L","I","d","D","i","t","T"]},R:{priority:130,parse:function(t,e,r,n){return Kt("R"===e?4:e.length,t)},set:function(t,e,r,n){var a=new Date(0);return a.setUTCFullYear(r,0,4),a.setUTCHours(0,0,0,0),W(a)},incompatibleTokens:["G","y","Y","u","Q","q","M","L","w","d","D","e","c","t","T"]},u:{priority:130,parse:function(t,e,r,n){return Kt("u"===e?4:e.length,t)},set:function(t,e,r,n){return t.setUTCFullYear(r,0,1),t.setUTCHours(0,0,0,0),t},incompatibleTokens:["G","y","Y","R","w","I","i","e","c","t","T"]},Q:{priority:120,parse:function(t,e,r,n){switch(e){case"Q":case"QQ":return Zt(e.length,t);case"Qo":return r.ordinalNumber(t,{unit:"quarter"});case"QQQ":return r.quarter(t,{width:"abbreviated",context:"formatting"})||r.quarter(t,{width:"narrow",context:"formatting"});case"QQQQQ":return r.quarter(t,{width:"narrow",context:"formatting"});default:return r.quarter(t,{width:"wide",context:"formatting"})||r.quarter(t,{width:"abbreviated",context:"formatting"})||r.quarter(t,{width:"narrow",context:"formatting"})}},validate:function(t,e,r){return e>=1&&e<=4},set:function(t,e,r,n){return t.setUTCMonth(3*(r-1),1),t.setUTCHours(0,0,0,0),t},incompatibleTokens:["Y","R","q","M","L","w","I","d","D","i","e","c","t","T"]},q:{priority:120,parse:function(t,e,r,n){switch(e){case"q":case"qq":return Zt(e.length,t);case"qo":return r.ordinalNumber(t,{unit:"quarter"});case"qqq":return r.quarter(t,{width:"abbreviated",context:"standalone"})||r.quarter(t,{width:"narrow",context:"standalone"});case"qqqqq":return r.quarter(t,{width:"narrow",context:"standalone"});default:return r.quarter(t,{width:"wide",context:"standalone"})||r.quarter(t,{width:"abbreviated",context:"standalone"})||r.quarter(t,{width:"narrow",context:"standalone"})}},validate:function(t,e,r){return e>=1&&e<=4},set:function(t,e,r,n){return t.setUTCMonth(3*(r-1),1),t.setUTCHours(0,0,0,0),t},incompatibleTokens:["Y","R","Q","M","L","w","I","d","D","i","e","c","t","T"]},M:{priority:110,parse:function(t,e,r,n){var a=function(t){return t-1};switch(e){case"M":return Bt(pt,t,a);case"MM":return Zt(2,t,a);case"Mo":return r.ordinalNumber(t,{unit:"month",valueCallback:a});case"MMM":return r.month(t,{width:"abbreviated",context:"formatting"})||r.month(t,{width:"narrow",context:"formatting"});case"MMMMM":return r.month(t,{width:"narrow",context:"formatting"});default:return r.month(t,{width:"wide",context:"formatting"})||r.month(t,{width:"abbreviated",context:"formatting"})||r.month(t,{width:"narrow",context:"formatting"})}},validate:function(t,e,r){return e>=0&&e<=11},set:function(t,e,r,n){return t.setUTCMonth(r,1),t.setUTCHours(0,0,0,0),t},incompatibleTokens:["Y","R","q","Q","L","w","I","D","i","e","c","t","T"]},L:{priority:110,parse:function(t,e,r,n){var a=function(t){return t-1};switch(e){case"L":return Bt(pt,t,a);case"LL":return Zt(2,t,a);case"Lo":return r.ordinalNumber(t,{unit:"month",valueCallback:a});case"LLL":return r.month(t,{width:"abbreviated",context:"standalone"})||r.month(t,{width:"narrow",context:"standalone"});case"LLLLL":return r.month(t,{width:"narrow",context:"standalone"});default:return r.month(t,{width:"wide",context:"standalone"})||r.month(t,{width:"abbreviated",context:"standalone"})||r.month(t,{width:"narrow",context:"standalone"})}},validate:function(t,e,r){return e>=0&&e<=11},set:function(t,e,r,n){return t.setUTCMonth(r,1),t.setUTCHours(0,0,0,0),t},incompatibleTokens:["Y","R","q","Q","M","w","I","D","i","e","c","t","T"]},w:{priority:100,parse:function(t,e,r,n){switch(e){case"w":return Bt(Dt,t);case"wo":return r.ordinalNumber(t,{unit:"week"});default:return Zt(e.length,t)}},validate:function(t,e,r){return e>=1&&e<=53},set:function(t,a,i,o){return G(function(t,a,i){r(2,arguments);var o=n(t),u=e(a),s=z(o,i)-u;return o.setUTCDate(o.getUTCDate()-7*s),o}(t,i,o),o)},incompatibleTokens:["y","R","u","q","Q","M","L","I","d","D","i","t","T"]},I:{priority:100,parse:function(t,e,r,n){switch(e){case"I":return Bt(Dt,t);case"Io":return r.ordinalNumber(t,{unit:"week"});default:return Zt(e.length,t)}},validate:function(t,e,r){return e>=1&&e<=53},set:function(t,a,i,o){return W(function(t,a){r(2,arguments);var i=n(t),o=e(a),u=I(i)-o;return i.setUTCDate(i.getUTCDate()-7*u),i}(t,i,o),o)},incompatibleTokens:["y","Y","u","q","Q","M","L","w","d","D","e","c","t","T"]},d:{priority:90,subPriority:1,parse:function(t,e,r,n){switch(e){case"d":return Bt(Ct,t);case"do":return r.ordinalNumber(t,{unit:"date"});default:return Zt(e.length,t)}},validate:function(t,e,r){var n=te(t.getUTCFullYear()),a=t.getUTCMonth();return n?e>=1&&e<=Vt[a]:e>=1&&e<=Jt[a]},set:function(t,e,r,n){return t.setUTCDate(r),t.setUTCHours(0,0,0,0),t},incompatibleTokens:["Y","R","q","Q","w","I","D","i","e","c","t","T"]},D:{priority:90,subPriority:1,parse:function(t,e,r,n){switch(e){case"D":case"DD":return Bt(Mt,t);case"Do":return r.ordinalNumber(t,{unit:"date"});default:return Zt(e.length,t)}},validate:function(t,e,r){return te(t.getUTCFullYear())?e>=1&&e<=366:e>=1&&e<=365},set:function(t,e,r,n){return t.setUTCMonth(0,r),t.setUTCHours(0,0,0,0),t},incompatibleTokens:["Y","R","q","Q","M","L","w","I","d","E","i","e","c","t","T"]},E:{priority:90,parse:function(t,e,r,n){switch(e){case"E":case"EE":case"EEE":return r.day(t,{width:"abbreviated",context:"formatting"})||r.day(t,{width:"short",context:"formatting"})||r.day(t,{width:"narrow",context:"formatting"});case"EEEEE":return r.day(t,{width:"narrow",context:"formatting"});case"EEEEEE":return r.day(t,{width:"short",context:"formatting"})||r.day(t,{width:"narrow",context:"formatting"});default:return r.day(t,{width:"wide",context:"formatting"})||r.day(t,{width:"abbreviated",context:"formatting"})||r.day(t,{width:"short",context:"formatting"})||r.day(t,{width:"narrow",context:"formatting"})}},validate:function(t,e,r){return e>=0&&e<=6},set:function(t,e,r,n){return(t=Tt(t,r,n)).setUTCHours(0,0,0,0),t},incompatibleTokens:["D","i","e","c","t","T"]},e:{priority:90,parse:function(t,e,r,n){var a=function(t){var e=7*Math.floor((t-1)/7);return(t+n.weekStartsOn+6)%7+e};switch(e){case"e":case"ee":return Zt(e.length,t,a);case"eo":return r.ordinalNumber(t,{unit:"day",valueCallback:a});case"eee":return r.day(t,{width:"abbreviated",context:"formatting"})||r.day(t,{width:"short",context:"formatting"})||r.day(t,{width:"narrow",context:"formatting"});case"eeeee":return r.day(t,{width:"narrow",context:"formatting"});case"eeeeee":return r.day(t,{width:"short",context:"formatting"})||r.day(t,{width:"narrow",context:"formatting"});default:return r.day(t,{width:"wide",context:"formatting"})||r.day(t,{width:"abbreviated",context:"formatting"})||r.day(t,{width:"short",context:"formatting"})||r.day(t,{width:"narrow",context:"formatting"})}},validate:function(t,e,r){return e>=0&&e<=6},set:function(t,e,r,n){return(t=Tt(t,r,n)).setUTCHours(0,0,0,0),t},incompatibleTokens:["y","R","u","q","Q","M","L","I","d","D","E","i","c","t","T"]},c:{priority:90,parse:function(t,e,r,n){var a=function(t){var e=7*Math.floor((t-1)/7);return(t+n.weekStartsOn+6)%7+e};switch(e){case"c":case"cc":return Zt(e.length,t,a);case"co":return r.ordinalNumber(t,{unit:"day",valueCallback:a});case"ccc":return r.day(t,{width:"abbreviated",context:"standalone"})||r.day(t,{width:"short",context:"standalone"})||r.day(t,{width:"narrow",context:"standalone"});case"ccccc":return r.day(t,{width:"narrow",context:"standalone"});case"cccccc":return r.day(t,{width:"short",context:"standalone"})||r.day(t,{width:"narrow",context:"standalone"});default:return r.day(t,{width:"wide",context:"standalone"})||r.day(t,{width:"abbreviated",context:"standalone"})||r.day(t,{width:"short",context:"standalone"})||r.day(t,{width:"narrow",context:"standalone"})}},validate:function(t,e,r){return e>=0&&e<=6},set:function(t,e,r,n){return(t=Tt(t,r,n)).setUTCHours(0,0,0,0),t},incompatibleTokens:["y","R","u","q","Q","M","L","I","d","D","E","i","e","t","T"]},i:{priority:90,parse:function(t,e,r,n){var a=function(t){return 0===t?7:t};switch(e){case"i":case"ii":return Zt(e.length,t);case"io":return r.ordinalNumber(t,{unit:"day"});case"iii":return r.day(t,{width:"abbreviated",context:"formatting",valueCallback:a})||r.day(t,{width:"short",context:"formatting",valueCallback:a})||r.day(t,{width:"narrow",context:"formatting",valueCallback:a});case"iiiii":return r.day(t,{width:"narrow",context:"formatting",valueCallback:a});case"iiiiii":return r.day(t,{width:"short",context:"formatting",valueCallback:a})||r.day(t,{width:"narrow",context:"formatting",valueCallback:a});default:return r.day(t,{width:"wide",context:"formatting",valueCallback:a})||r.day(t,{width:"abbreviated",context:"formatting",valueCallback:a})||r.day(t,{width:"short",context:"formatting",valueCallback:a})||r.day(t,{width:"narrow",context:"formatting",valueCallback:a})}},validate:function(t,e,r){return e>=1&&e<=7},set:function(t,a,i,o){return t=function(t,a){r(2,arguments);var i=e(a);i%7==0&&(i-=7);var o=1,u=n(t),s=u.getUTCDay(),c=((i%7+7)%7=1&&e<=12},set:function(t,e,r,n){var a=t.getUTCHours()>=12;return a&&r<12?t.setUTCHours(r+12,0,0,0):a||12!==r?t.setUTCHours(r,0,0,0):t.setUTCHours(0,0,0,0),t},incompatibleTokens:["H","K","k","t","T"]},H:{priority:70,parse:function(t,e,r,n){switch(e){case"H":return Bt(xt,t);case"Ho":return r.ordinalNumber(t,{unit:"hour"});default:return Zt(e.length,t)}},validate:function(t,e,r){return e>=0&&e<=23},set:function(t,e,r,n){return t.setUTCHours(r,0,0,0),t},incompatibleTokens:["a","b","h","K","k","t","T"]},K:{priority:70,parse:function(t,e,r,n){switch(e){case"K":return Bt(Ut,t);case"Ko":return r.ordinalNumber(t,{unit:"hour"});default:return Zt(e.length,t)}},validate:function(t,e,r){return e>=0&&e<=11},set:function(t,e,r,n){return t.getUTCHours()>=12&&r<12?t.setUTCHours(r+12,0,0,0):t.setUTCHours(r,0,0,0),t},incompatibleTokens:["a","b","h","H","k","t","T"]},k:{priority:70,parse:function(t,e,r,n){switch(e){case"k":return Bt(kt,t);case"ko":return r.ordinalNumber(t,{unit:"hour"});default:return Zt(e.length,t)}},validate:function(t,e,r){return e>=1&&e<=24},set:function(t,e,r,n){var a=r<=24?r%24:r;return t.setUTCHours(a,0,0,0),t},incompatibleTokens:["a","b","h","H","K","t","T"]},m:{priority:60,parse:function(t,e,r,n){switch(e){case"m":return Bt(Nt,t);case"mo":return r.ordinalNumber(t,{unit:"minute"});default:return Zt(e.length,t)}},validate:function(t,e,r){return e>=0&&e<=59},set:function(t,e,r,n){return t.setUTCMinutes(r,0,0),t},incompatibleTokens:["t","T"]},s:{priority:50,parse:function(t,e,r,n){switch(e){case"s":return Bt(St,t);case"so":return r.ordinalNumber(t,{unit:"second"});default:return Zt(e.length,t)}},validate:function(t,e,r){return e>=0&&e<=59},set:function(t,e,r,n){return t.setUTCSeconds(r,0),t},incompatibleTokens:["t","T"]},S:{priority:30,parse:function(t,e,r,n){return Zt(e.length,t,(function(t){return Math.floor(t*Math.pow(10,3-e.length))}))},set:function(t,e,r,n){return t.setUTCMilliseconds(r),t},incompatibleTokens:["t","T"]},X:{priority:10,parse:function(t,e,r,n){switch(e){case"X":return zt(Rt,t);case"XX":return zt(It,t);case"XXXX":return zt(Gt,t);case"XXXXX":return zt(jt,t);default:return zt(Xt,t)}},set:function(t,e,r,n){return e.timestampIsSet?t:new Date(t.getTime()-r)},incompatibleTokens:["t","T","x"]},x:{priority:10,parse:function(t,e,r,n){switch(e){case"x":return zt(Rt,t);case"xx":return zt(It,t);case"xxxx":return zt(Gt,t);case"xxxxx":return zt(jt,t);default:return zt(Xt,t)}},set:function(t,e,r,n){return e.timestampIsSet?t:new Date(t.getTime()-r)},incompatibleTokens:["t","T","X"]},t:{priority:40,parse:function(t,e,r,n){return At(t)},set:function(t,e,r,n){return[new Date(1e3*r),{timestampIsSet:!0}]},incompatibleTokens:"*"},T:{priority:20,parse:function(t,e,r,n){return At(t)},set:function(t,e,r,n){return[new Date(r),{timestampIsSet:!0}]},incompatibleTokens:"*"}},re=ee,ne=/[yYQqMLwIdDecihHKkms]o|(\w)\1*|''|'(''|[^'])+('|$)|./g,ae=/P+p+|P+|p+|''|'(''|[^'])+('|$)|./g,ie=/^'([^]*?)'?$/,oe=/''/g,ue=/\S/,se=/[a-zA-Z]/;function ce(t,e){if(e.timestampIsSet)return t;var r=new Date(0);return r.setFullYear(t.getUTCFullYear(),t.getUTCMonth(),t.getUTCDate()),r.setHours(t.getUTCHours(),t.getUTCMinutes(),t.getUTCSeconds(),t.getUTCMilliseconds()),r}function de(t){return t.match(ie)[1].replace(oe,"'")}var le=36e5,fe={dateTimeDelimiter:/[T ]/,timeZoneDelimiter:/[Z ]/i,timezone:/([Z+-].*)$/},he=/^-?(?:(\d{3})|(\d{2})(?:-?(\d{2}))?|W(\d{2})(?:-?(\d{1}))?|)$/,me=/^(\d{2}(?:[.,]\d*)?)(?::?(\d{2}(?:[.,]\d*)?))?(?::?(\d{2}(?:[.,]\d*)?))?$/,we=/^([+-])(\d{2})(?::?(\d{2}))?$/;function ge(t){var e,r={},n=t.split(fe.dateTimeDelimiter);if(n.length>2)return r;if(/:/.test(n[0])?(r.date=null,e=n[0]):(r.date=n[0],e=n[1],fe.timeZoneDelimiter.test(r.date)&&(r.date=t.split(fe.timeZoneDelimiter)[0],e=t.substr(r.date.length,t.length))),e){var a=fe.timezone.exec(e);a?(r.time=e.replace(a[1],""),r.timezone=a[1]):r.time=e}return r}function ve(t,e){var r=new RegExp("^(?:(\\d{4}|[+-]\\d{"+(4+e)+"})|(\\d{2}|[+-]\\d{"+(2+e)+"})$)"),n=t.match(r);if(!n)return{year:null};var a=n[1]&&parseInt(n[1]),i=n[2]&&parseInt(n[2]);return{year:null==i?a:100*i,restDateString:t.slice((n[1]||n[2]).length)}}function ye(t,e){if(null===e)return null;var r=t.match(he);if(!r)return null;var n=!!r[4],a=be(r[1]),i=be(r[2])-1,o=be(r[3]),u=be(r[4]),s=be(r[5])-1;if(n)return function(t,e,r){return e>=1&&e<=53&&r>=0&&r<=6}(0,u,s)?function(t,e,r){var n=new Date(0);n.setUTCFullYear(t,0,4);var a=n.getUTCDay()||7,i=7*(e-1)+r+1-a;return n.setUTCDate(n.getUTCDate()+i),n}(e,u,s):new Date(NaN);var c=new Date(0);return function(t,e,r){return e>=0&&e<=11&&r>=1&&r<=(Me[e]||(De(t)?29:28))}(e,i,o)&&function(t,e){return e>=1&&e<=(De(t)?366:365)}(e,a)?(c.setUTCFullYear(e,i,Math.max(a,o)),c):new Date(NaN)}function be(t){return t?parseInt(t):1}function Te(t){var e=t.match(me);if(!e)return null;var r=pe(e[1]),n=pe(e[2]),a=pe(e[3]);return function(t,e,r){if(24===t)return 0===e&&0===r;return r>=0&&r<60&&e>=0&&e<60&&t>=0&&t<25}(r,n,a)?r*le+6e4*n+1e3*a:NaN}function pe(t){return t&&parseFloat(t.replace(",","."))||0}function Ce(t){if("Z"===t)return 0;var e=t.match(we);if(!e)return 0;var r="+"===e[1]?-1:1,n=parseInt(e[2]),a=e[3]&&parseInt(e[3])||0;return function(t,e){return e>=0&&e<=59}(0,a)?r*(n*le+6e4*a):NaN}var Me=[31,null,31,30,31,30,31,31,30,31,30,31];function De(t){return t%400==0||t%4==0&&t%100}const xe={datetime:"MMM d, yyyy, h:mm:ss aaaa",millisecond:"h:mm:ss.SSS aaaa",second:"h:mm:ss aaaa",minute:"h:mm aaaa",hour:"ha",day:"MMM d",week:"PP",month:"MMM yyyy",quarter:"qqq - yyyy",year:"yyyy"};t._adapters._date.override({_id:"date-fns",formats:function(){return xe},parse:function(t,a){if(null==t)return null;const i=typeof t;return"number"===i||t instanceof Date?t=n(t):"string"===i&&(t="string"==typeof a?function(t,a,i,o){r(3,arguments);var u=String(t),s=String(a),d=o||{},l=d.locale||q;if(!l.match)throw new RangeError("locale must contain match property");var f=l.options&&l.options.firstWeekContainsDate,h=null==f?1:e(f),m=null==d.firstWeekContainsDate?h:e(d.firstWeekContainsDate);if(!(m>=1&&m<=7))throw new RangeError("firstWeekContainsDate must be between 1 and 7 inclusively");var w=l.options&&l.options.weekStartsOn,g=null==w?0:e(w),v=null==d.weekStartsOn?g:e(d.weekStartsOn);if(!(v>=0&&v<=6))throw new RangeError("weekStartsOn must be between 0 and 6 inclusively");if(""===s)return""===u?n(i):new Date(NaN);var y,b={firstWeekContainsDate:m,weekStartsOn:v,locale:l},T=[{priority:10,subPriority:-1,set:ce,index:0}],p=s.match(ae).map((function(t){var e=t[0];return"p"===e||"P"===e?(0,ut[e])(t,l.formatLong,b):t})).join("").match(ne),C=[];for(y=0;y0&&ue.test(u))return new Date(NaN);var P=T.map((function(t){return t.priority})).sort((function(t,e){return e-t})).filter((function(t,e,r){return r.indexOf(t)===e})).map((function(t){return T.filter((function(e){return e.priority===t})).sort((function(t,e){return e.subPriority-t.subPriority}))})).map((function(t){return t[0]})),E=n(i);if(isNaN(E))return new Date(NaN);var O=H(E,c(E)),F={};for(y=0;y=1&&f<=7))throw new RangeError("firstWeekContainsDate must be between 1 and 7 inclusively");var h=s.options&&s.options.weekStartsOn,w=null==h?0:e(h),g=null==u.weekStartsOn?w:e(u.weekStartsOn);if(!(g>=0&&g<=6))throw new RangeError("weekStartsOn must be between 0 and 6 inclusively");if(!s.localize)throw new RangeError("locale must contain localize property");if(!s.formatLong)throw new RangeError("locale must contain formatLong property");var v=n(t);if(!m(v))throw new RangeError("Invalid time value");var y=c(v),b=H(v,y),T={firstWeekContainsDate:f,weekStartsOn:g,locale:s,_originalDate:v},p=o.match(mt).map((function(t){var e=t[0];return"p"===e||"P"===e?(0,ut[e])(t,s.formatLong,T):t})).join("").match(ht).map((function(e){if("''"===e)return"'";var r=e[0];if("'"===r)return yt(e);var n=nt[r];if(n)return!u.useAdditionalWeekYearTokens&<(e)&&ft(e,a,t),!u.useAdditionalDayOfYearTokens&&dt(e)&&ft(e,a,t),n(b,e,s.localize,T);if(r.match(vt))throw new RangeError("Format string contains an unescaped latin alphabet character `"+r+"`");return e})).join("");return p}(t,a,this.options)},add:function(t,n,s){switch(s){case"millisecond":return o(t,n);case"second":return function(t,n){r(2,arguments);var a=e(n);return o(t,1e3*a)}(t,n);case"minute":return function(t,n){r(2,arguments);var a=e(n);return o(t,6e4*a)}(t,n);case"hour":return function(t,n){r(2,arguments);var a=e(n);return o(t,a*u)}(t,n);case"day":return a(t,n);case"week":return function(t,n){r(2,arguments);var i=e(n),o=7*i;return a(t,o)}(t,n);case"month":return i(t,n);case"quarter":return function(t,n){r(2,arguments);var a=e(n),o=3*a;return i(t,o)}(t,n);case"year":return function(t,n){r(2,arguments);var a=e(n);return i(t,12*a)}(t,n);default:return t}},diff:function(t,e,a){switch(a){case"millisecond":return b(t,e);case"second":return function(t,e){r(2,arguments);var n=b(t,e)/1e3;return n>0?Math.floor(n):Math.ceil(n)}(t,e);case"minute":return function(t,e){r(2,arguments);var n=b(t,e)/6e4;return n>0?Math.floor(n):Math.ceil(n)}(t,e);case"hour":return function(t,e){r(2,arguments);var n=b(t,e)/T;return n>0?Math.floor(n):Math.ceil(n)}(t,e);case"day":return y(t,e);case"week":return function(t,e){r(2,arguments);var n=y(t,e)/7;return n>0?Math.floor(n):Math.ceil(n)}(t,e);case"month":return D(t,e);case"quarter":return function(t,e){r(2,arguments);var n=D(t,e)/3;return n>0?Math.floor(n):Math.ceil(n)}(t,e);case"year":return function(t,e){r(2,arguments);var a=n(t),i=n(e),o=h(a,i),u=Math.abs(g(a,i));a.setFullYear("1584"),i.setFullYear("1584");var s=h(a,i)===-o,c=o*(u-s);return 0===c?0:c}(t,e);default:return 0}},startOf:function(t,e,a){switch(e){case"second":return function(t){r(1,arguments);var e=n(t);return e.setMilliseconds(0),e}(t);case"minute":return function(t){r(1,arguments);var e=n(t);return e.setSeconds(0,0),e}(t);case"hour":return function(t){r(1,arguments);var e=n(t);return e.setMinutes(0,0,0),e}(t);case"day":return d(t);case"week":return s(t);case"isoWeek":return s(t,{weekStartsOn:+a});case"month":return function(t){r(1,arguments);var e=n(t);return e.setDate(1),e.setHours(0,0,0,0),e}(t);case"quarter":return function(t){r(1,arguments);var e=n(t),a=e.getMonth(),i=a-a%3;return e.setMonth(i,1),e.setHours(0,0,0,0),e}(t);case"year":return function(t){r(1,arguments);var e=n(t),a=new Date(0);return a.setFullYear(e.getFullYear(),0,1),a.setHours(0,0,0,0),a}(t);default:return t}},endOf:function(t,a){switch(a){case"second":return function(t){r(1,arguments);var e=n(t);return e.setMilliseconds(999),e}(t);case"minute":return function(t){r(1,arguments);var e=n(t);return e.setSeconds(59,999),e}(t);case"hour":return function(t){r(1,arguments);var e=n(t);return e.setMinutes(59,59,999),e}(t);case"day":return p(t);case"week":return function(t,a){r(1,arguments);var i=a||{},o=i.locale,u=o&&o.options&&o.options.weekStartsOn,s=null==u?0:e(u),c=null==i.weekStartsOn?s:e(i.weekStartsOn);if(!(c>=0&&c<=6))throw new RangeError("weekStartsOn must be between 0 and 6 inclusively");var d=n(t),l=d.getDay(),f=6+(l 0) { - streaming.intervalId = setInterval(() => { - if (!me.realtime.pause && typeof me.realtime.onRefresh === 'function') { - me.realtime.onRefresh(chart); - } - me.update(); - chart.update('quiet'); - }, me.realtime.refresh); - } - } - - update(args) { - const me = this; - const chart = me.chart; - - if (!chart.data || !chart.data.datasets) { - return; - } - - const now = Date.now(); - const duration = me.realtime.duration; - const delay = me.realtime.delay; - const ttl = me.realtime.ttl; - - // Calcular ventana de tiempo - me.max = now - delay; - me.min = me.max - duration; - - // Limpiar datos antiguos si TTL está configurado - if (ttl) { - const cutoff = now - ttl; - chart.data.datasets.forEach(dataset => { - if (dataset.data) { - dataset.data = dataset.data.filter(point => { - return point.x > cutoff; - }); - } - }); - } - - super.update(args); - } - - destroy() { - const me = this; - const chart = me.chart; - const streaming = chart.$streaming; - - if (streaming && streaming.intervalId) { - clearInterval(streaming.intervalId); - delete streaming.intervalId; - } - - super.destroy(); - } - - static id = 'realtime'; - static defaults = { - realtime: { - duration: 10000, - delay: 0, - refresh: 1000, - frameRate: 30, - pause: false, - ttl: undefined, - onRefresh: null - }, - time: { - unit: 'second', - displayFormats: { - second: 'HH:mm:ss' - } - } - }; - } - - // ============= PLUGIN.STREAMING.JS (Simplificado) ============= - const streamingPlugin = { - id: 'streaming', - - beforeInit(chart) { - const streaming = chart.$streaming = chart.$streaming || {}; - streaming.enabled = false; - - // Detectar si hay escalas realtime - const scales = chart.options.scales || {}; - Object.keys(scales).forEach(scaleId => { - if (scales[scaleId].type === 'realtime') { - streaming.enabled = true; - } - }); - }, - - afterInit(chart) { - const streaming = chart.$streaming; - if (streaming && streaming.enabled) { - // Configurar actualización automática - const update = chart.update; - chart.update = function (mode) { - if (mode === 'quiet') { - // Actualización silenciosa para streaming - Chart.prototype.update.call(this, mode); - } else { - update.call(this, mode); - } - }; - } - }, - - beforeUpdate(chart) { - const streaming = chart.$streaming; - if (!streaming || !streaming.enabled) return; - - // Permitir que las líneas Bézier se extiendan fuera del área del gráfico - const elements = chart.options.elements || {}; - if (elements.line) { - elements.line.capBezierPoints = false; - } - }, - - destroy(chart) { - const streaming = chart.$streaming; - if (streaming && streaming.intervalId) { - clearInterval(streaming.intervalId); - delete streaming.intervalId; - } - delete chart.$streaming; - } - }; - - // ============= REGISTRO DE COMPONENTES ============= - - // Registrar escala realtime - Chart.register(RealTimeScale); - - // Registrar plugin de streaming - Chart.register(streamingPlugin); - - // ============= UTILIDADES PARA LA APLICACIÓN ============= - - /** - * Crea una configuración de Chart.js optimizada para streaming - */ - function createStreamingChartConfig(options = {}) { - const config = { - type: 'line', - data: { - datasets: [] - }, - options: { - responsive: true, - maintainAspectRatio: false, - animation: false, // Desactivar animaciones para mejor performance - - scales: { - x: { - type: 'realtime', - realtime: { - duration: options.duration || 60000, // 60 segundos por defecto - delay: options.delay || 0, - refresh: options.refresh || 1000, // 1 segundo - frameRate: options.frameRate || 30, - pause: options.pause || false, - ttl: options.ttl || undefined, - onRefresh: options.onRefresh || null - }, - title: { - display: true, - text: 'Tiempo' - } - }, - y: { - title: { - display: true, - text: 'Valor' - }, - min: options.yMin, - max: options.yMax - } - }, - - plugins: { - legend: { - display: true, - position: 'top' - }, - tooltip: { - mode: 'index', - intersect: false - } - }, - - interaction: { - mode: 'nearest', - axis: 'x', - intersect: false - }, - - elements: { - point: { - radius: 0, // Sin puntos para mejor performance - hoverRadius: 3 - }, - line: { - tension: 0.1, - borderWidth: 2 - } - } - }, - plugins: ['streaming'] - }; - - return config; - } - - /** - * Agrega datos a un dataset de streaming - */ - function addStreamingData(chart, datasetIndex, data) { - if (!chart || !chart.data || !chart.data.datasets[datasetIndex]) { - return; - } - - const dataset = chart.data.datasets[datasetIndex]; - if (!dataset.data) { - dataset.data = []; - } - - // Agregar nuevo punto con timestamp - const timestamp = data.x || Date.now(); - dataset.data.push({ - x: timestamp, - y: data.y - }); - - // Chart.js se encarga automáticamente de eliminar datos antiguos - // basado en la configuración de TTL y duration de la escala realtime - } - - /** - * Controla la pausa/reanudación del streaming - */ - function setStreamingPause(chart, paused) { - if (!chart || !chart.$streaming) return; - - const scales = chart.scales; - Object.keys(scales).forEach(scaleId => { - const scale = scales[scaleId]; - if (scale instanceof RealTimeScale) { - scale.realtime.pause = paused; - } - }); - } - - /** - * Limpia todos los datos de streaming - */ - function clearStreamingData(chart) { - if (!chart || !chart.data) return; - - chart.data.datasets.forEach(dataset => { - if (dataset.data) { - dataset.data.length = 0; - } - }); - - chart.update('quiet'); - } - - // ============= EXPORTS ============= - - // Exportar para uso en la aplicación - exports.RealTimeScale = RealTimeScale; - exports.streamingPlugin = streamingPlugin; - exports.createStreamingChartConfig = createStreamingChartConfig; - exports.addStreamingData = addStreamingData; - exports.setStreamingPause = setStreamingPause; - exports.clearStreamingData = clearStreamingData; - - // Hacer disponible globalmente - if (typeof window !== 'undefined') { - window.ChartStreaming = { - createStreamingChartConfig, - addStreamingData, - setStreamingPause, - clearStreamingData, - RealTimeScale, - streamingPlugin - }; - } - - console.log('📈 Chart.js Streaming Plugin loaded successfully'); -}); \ No newline at end of file diff --git a/web_test/PLC S7-315 Streamer & Logger_files/csv.js.descargar b/web_test/PLC S7-315 Streamer & Logger_files/csv.js.descargar deleted file mode 100644 index 50e1314..0000000 --- a/web_test/PLC S7-315 Streamer & Logger_files/csv.js.descargar +++ /dev/null @@ -1,170 +0,0 @@ -/** - * Gestión de la configuración CSV y operaciones relacionadas - */ - -// Cargar configuración CSV -function loadCsvConfig() { - fetch('/api/csv/config') - .then(response => response.json()) - .then(data => { - if (data.success) { - const config = data.config; - - // Actualizar elementos de visualización - document.getElementById('csv-directory-path').textContent = config.current_directory || 'N/A'; - document.getElementById('csv-rotation-enabled').textContent = config.rotation_enabled ? '✅ Yes' : '❌ No'; - document.getElementById('csv-max-size').textContent = config.max_size_mb ? `${config.max_size_mb} MB` : 'No limit'; - document.getElementById('csv-max-days').textContent = config.max_days ? `${config.max_days} days` : 'No limit'; - document.getElementById('csv-max-hours').textContent = config.max_hours ? `${config.max_hours} hours` : 'No limit'; - document.getElementById('csv-cleanup-interval').textContent = `${config.cleanup_interval_hours} hours`; - - // Actualizar campos del formulario - document.getElementById('records-directory').value = config.records_directory || ''; - document.getElementById('rotation-enabled').checked = config.rotation_enabled || false; - document.getElementById('max-size-mb').value = config.max_size_mb || ''; - document.getElementById('max-days').value = config.max_days || ''; - document.getElementById('max-hours').value = config.max_hours || ''; - document.getElementById('cleanup-interval').value = config.cleanup_interval_hours || 24; - - // Cargar información del directorio - loadCsvDirectoryInfo(); - } else { - showMessage('Error loading CSV configuration: ' + data.message, 'error'); - } - }) - .catch(error => { - showMessage('Error loading CSV configuration', 'error'); - }); -} - -// Cargar información del directorio CSV -function loadCsvDirectoryInfo() { - fetch('/api/csv/directory/info') - .then(response => response.json()) - .then(data => { - if (data.success) { - const info = data.info; - const statsDiv = document.getElementById('directory-stats'); - - let html = ` -

- 📁 Directory: - ${info.base_directory} -
-
- 📊 Total Files: - ${info.total_files} -
-
- 💾 Total Size: - ${info.total_size_mb} MB -
- `; - - if (info.oldest_file) { - html += ` -
- 📅 Oldest File: - ${new Date(info.oldest_file).toLocaleString()} -
- `; - } - - if (info.newest_file) { - html += ` -
- 🆕 Newest File: - ${new Date(info.newest_file).toLocaleString()} -
- `; - } - - if (info.day_folders && info.day_folders.length > 0) { - html += '

📂 Day Folders:

'; - info.day_folders.forEach(folder => { - html += ` -
- ${folder.name} - ${folder.files} files, ${folder.size_mb} MB -
- `; - }); - } - - statsDiv.innerHTML = html; - } - }) - .catch(error => { - document.getElementById('directory-stats').innerHTML = '

Error loading directory information

'; - }); -} - -// Ejecutar limpieza manual -function triggerManualCleanup() { - if (!confirm('¿Estás seguro de que quieres ejecutar la limpieza manual? Esto eliminará archivos antiguos según la configuración actual.')) { - return; - } - - fetch('/api/csv/cleanup', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - } - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - showMessage('Limpieza ejecutada correctamente', 'success'); - loadCsvDirectoryInfo(); // Recargar información del directorio - } else { - showMessage('Error en la limpieza: ' + data.message, 'error'); - } - }) - .catch(error => { - showMessage('Error ejecutando la limpieza', 'error'); - }); -} - -// Inicializar listeners para la configuración CSV -function initCsvListeners() { - // Manejar envío del formulario de configuración CSV - document.getElementById('csv-config-form').addEventListener('submit', function (e) { - e.preventDefault(); - - const formData = new FormData(e.target); - const configData = {}; - - // Convertir datos del formulario a objeto, manejando valores vacíos - for (let [key, value] of formData.entries()) { - if (key === 'rotation_enabled') { - configData[key] = document.getElementById('rotation-enabled').checked; - } else if (value.trim() === '') { - configData[key] = null; - } else if (key.includes('max_') || key.includes('cleanup_interval')) { - configData[key] = parseFloat(value) || null; - } else { - configData[key] = value.trim(); - } - } - - fetch('/api/csv/config', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(configData) - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - showMessage('Configuración CSV actualizada correctamente', 'success'); - loadCsvConfig(); // Recargar para mostrar valores actualizados - } else { - showMessage('Error actualizando configuración CSV: ' + data.message, 'error'); - } - }) - .catch(error => { - showMessage('Error actualizando configuración CSV', 'error'); - }); - }); -} \ No newline at end of file diff --git a/web_test/PLC S7-315 Streamer & Logger_files/datasets.js.descargar b/web_test/PLC S7-315 Streamer & Logger_files/datasets.js.descargar deleted file mode 100644 index bdf5d98..0000000 --- a/web_test/PLC S7-315 Streamer & Logger_files/datasets.js.descargar +++ /dev/null @@ -1,519 +0,0 @@ -/** - * Gestión de datasets y variables asociadas - */ - -// Variables de gestión de datasets -let currentDatasets = {}; -let currentDatasetId = null; - -// Cargar todos los datasets desde API -window.loadDatasets = function () { - fetch('/api/datasets') - .then(response => response.json()) - .then(data => { - if (data.success) { - currentDatasets = data.datasets; - currentDatasetId = data.current_dataset_id; - updateDatasetSelector(); - updateDatasetInfo(); - } - }) - .catch(error => { - console.error('Error loading datasets:', error); - showMessage('Error loading datasets', 'error'); - }); -} - -// Actualizar el selector de datasets -function updateDatasetSelector() { - const selector = document.getElementById('dataset-selector'); - selector.innerHTML = ''; - - Object.keys(currentDatasets).forEach(datasetId => { - const dataset = currentDatasets[datasetId]; - const option = document.createElement('option'); - option.value = datasetId; - option.textContent = `${dataset.name} (${dataset.prefix})`; - if (datasetId === currentDatasetId) { - option.selected = true; - } - selector.appendChild(option); - }); -} - -// Actualizar información del dataset -function updateDatasetInfo() { - const statusBar = document.getElementById('dataset-status-bar'); - const variablesManagement = document.getElementById('variables-management'); - const noDatasetMessage = document.getElementById('no-dataset-message'); - - if (currentDatasetId && currentDatasets[currentDatasetId]) { - const dataset = currentDatasets[currentDatasetId]; - - // Mostrar info del dataset en la barra de estado - document.getElementById('dataset-name').textContent = dataset.name; - document.getElementById('dataset-prefix').textContent = dataset.prefix; - document.getElementById('dataset-sampling').textContent = - dataset.sampling_interval ? `${dataset.sampling_interval}s` : 'Global interval'; - document.getElementById('dataset-var-count').textContent = Object.keys(dataset.variables).length; - document.getElementById('dataset-stream-count').textContent = dataset.streaming_variables.length; - - // Actualizar estado del dataset - const statusSpan = document.getElementById('dataset-status'); - const isActive = dataset.enabled; - statusSpan.textContent = isActive ? '🟢 Active' : '⭕ Inactive'; - statusSpan.className = `status-item ${isActive ? 'status-active' : 'status-inactive'}`; - - // Actualizar botones de acción - document.getElementById('activate-dataset-btn').style.display = isActive ? 'none' : 'inline-block'; - document.getElementById('deactivate-dataset-btn').style.display = isActive ? 'inline-block' : 'none'; - - // Mostrar secciones - statusBar.style.display = 'block'; - variablesManagement.style.display = 'block'; - noDatasetMessage.style.display = 'none'; - - // Cargar variables para este dataset - loadDatasetVariables(currentDatasetId); - } else { - statusBar.style.display = 'none'; - variablesManagement.style.display = 'none'; - noDatasetMessage.style.display = 'block'; - } -} - -// Cargar variables para un dataset específico -function loadDatasetVariables(datasetId) { - if (!datasetId || !currentDatasets[datasetId]) { - // Limpiar la tabla si no hay dataset válido - document.getElementById('variables-tbody').innerHTML = ''; - return; - } - - const dataset = currentDatasets[datasetId]; - const variables = dataset.variables || {}; - const streamingVars = dataset.streaming_variables || []; - const tbody = document.getElementById('variables-tbody'); - - // Limpiar filas existentes - tbody.innerHTML = ''; - - // Añadir una fila para cada variable - Object.keys(variables).forEach(varName => { - const variable = variables[varName]; - const row = document.createElement('tr'); - - // Formatear visualización del área de memoria - let memoryAreaDisplay = ''; - if (variable.area === 'db') { - memoryAreaDisplay = `DB${variable.db || 'N/A'}.${variable.offset}`; - } else if (variable.area === 'mw' || variable.area === 'm') { - memoryAreaDisplay = `MW${variable.offset}`; - } else if (variable.area === 'pew' || variable.area === 'pe') { - memoryAreaDisplay = `PEW${variable.offset}`; - } else if (variable.area === 'paw' || variable.area === 'pa') { - memoryAreaDisplay = `PAW${variable.offset}`; - } else if (variable.area === 'e') { - memoryAreaDisplay = `E${variable.offset}.${variable.bit}`; - } else if (variable.area === 'a') { - memoryAreaDisplay = `A${variable.offset}.${variable.bit}`; - } else if (variable.area === 'mb') { - memoryAreaDisplay = `M${variable.offset}.${variable.bit}`; - } else { - memoryAreaDisplay = `${variable.area.toUpperCase()}${variable.offset}`; - } - - // Comprobar si la variable está en la lista de streaming - const isStreaming = streamingVars.includes(varName); - - row.innerHTML = ` - ${varName} - ${memoryAreaDisplay} - ${variable.offset} - ${variable.type.toUpperCase()} - - -- - - - - - - - - - `; - - tbody.appendChild(row); - }); -} - -// Inicializar listeners de eventos para datasets -function initDatasetListeners() { - // Cambio de selector de dataset - document.getElementById('dataset-selector').addEventListener('change', function () { - const selectedDatasetId = this.value; - if (selectedDatasetId) { - // Detener streaming de variables actual si está activo - if (isStreamingVariables) { - stopVariableStreaming(); - } - - // Establecer como dataset actual - fetch('/api/datasets/current', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ dataset_id: selectedDatasetId }) - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - currentDatasetId = selectedDatasetId; - // Recargar datasets para obtener datos frescos, luego actualizar info - loadDatasets(); - - // Actualizar texto del botón de streaming - const toggleBtn = document.getElementById('toggle-streaming-btn'); - if (toggleBtn) { - toggleBtn.innerHTML = '▶️ Start Live Streaming'; - } - - // Auto-refrescar valores para el nuevo dataset - autoStartLiveDisplay(); - } else { - showMessage(data.message, 'error'); - } - }) - .catch(error => { - showMessage('Error setting current dataset', 'error'); - }); - } else { - // Detener streaming de variables si está activo - if (isStreamingVariables) { - stopVariableStreaming(); - } - - currentDatasetId = null; - updateDatasetInfo(); - // Limpiar valores cuando no hay dataset seleccionado - clearVariableValues(); - } - }); - - // Botón de nuevo dataset - document.getElementById('new-dataset-btn').addEventListener('click', function () { - document.getElementById('dataset-modal').style.display = 'block'; - }); - - // Cerrar modal de dataset - document.getElementById('close-dataset-modal').addEventListener('click', function () { - document.getElementById('dataset-modal').style.display = 'none'; - }); - - document.getElementById('cancel-dataset-btn').addEventListener('click', function () { - document.getElementById('dataset-modal').style.display = 'none'; - }); - - // Crear nuevo dataset - document.getElementById('dataset-form').addEventListener('submit', function (e) { - e.preventDefault(); - - const data = { - dataset_id: document.getElementById('dataset-id').value.trim(), - name: document.getElementById('dataset-name-input').value.trim(), - prefix: document.getElementById('dataset-prefix-input').value.trim(), - sampling_interval: document.getElementById('dataset-sampling-input').value || null - }; - - if (data.sampling_interval) { - data.sampling_interval = parseFloat(data.sampling_interval); - } - - fetch('/api/datasets', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(data) - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - showMessage(data.message, 'success'); - document.getElementById('dataset-modal').style.display = 'none'; - document.getElementById('dataset-form').reset(); - loadDatasets(); - } else { - showMessage(data.message, 'error'); - } - }) - .catch(error => { - showMessage('Error creating dataset', 'error'); - }); - }); - - // Botón de eliminar dataset - document.getElementById('delete-dataset-btn').addEventListener('click', function () { - if (!currentDatasetId) { - showMessage('No dataset selected', 'error'); - return; - } - - const dataset = currentDatasets[currentDatasetId]; - if (confirm(`Are you sure you want to delete dataset "${dataset.name}"?`)) { - fetch(`/api/datasets/${currentDatasetId}`, { - method: 'DELETE' - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - showMessage(data.message, 'success'); - loadDatasets(); - } else { - showMessage(data.message, 'error'); - } - }) - .catch(error => { - showMessage('Error deleting dataset', 'error'); - }); - } - }); - - // Botón de activar dataset - document.getElementById('activate-dataset-btn').addEventListener('click', function () { - if (!currentDatasetId) return; - - fetch(`/api/datasets/${currentDatasetId}/activate`, { - method: 'POST' - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - showMessage(data.message, 'success'); - loadDatasets(); - } else { - showMessage(data.message, 'error'); - } - }) - .catch(error => { - showMessage('Error activating dataset', 'error'); - }); - }); - - // Botón de desactivar dataset - document.getElementById('deactivate-dataset-btn').addEventListener('click', function () { - if (!currentDatasetId) return; - - fetch(`/api/datasets/${currentDatasetId}/deactivate`, { - method: 'POST' - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - showMessage(data.message, 'success'); - loadDatasets(); - } else { - showMessage(data.message, 'error'); - } - }) - .catch(error => { - showMessage('Error deactivating dataset', 'error'); - }); - }); - - // Formulario de variables - document.getElementById('variable-form').addEventListener('submit', function (e) { - e.preventDefault(); - - if (!currentDatasetId) { - showMessage('No dataset selected. Please select a dataset first.', 'error'); - return; - } - - const area = document.getElementById('var-area').value; - const data = { - name: document.getElementById('var-name').value, - area: area, - db: area === 'db' ? parseInt(document.getElementById('var-db').value) : 1, - offset: parseInt(document.getElementById('var-offset').value), - type: document.getElementById('var-type').value, - streaming: false // Default to not streaming - }; - - // Añadir parámetro bit para áreas de bit - if (area === 'e' || area === 'a' || area === 'mb') { - data.bit = parseInt(document.getElementById('var-bit').value); - } - - fetch(`/api/datasets/${currentDatasetId}/variables`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(data) - }) - .then(response => response.json()) - .then(data => { - showMessage(data.message, data.success ? 'success' : 'error'); - if (data.success) { - document.getElementById('variable-form').reset(); - loadDatasets(); // Recargar para actualizar conteos - updateStatus(); - } - }); - }); -} - -// Eliminar variable del dataset actual -function removeVariable(name) { - if (!currentDatasetId) { - showMessage('No dataset selected', 'error'); - return; - } - - if (confirm(`Are you sure you want to remove the variable "${name}" from this dataset?`)) { - fetch(`/api/datasets/${currentDatasetId}/variables/${name}`, { method: 'DELETE' }) - .then(response => response.json()) - .then(data => { - showMessage(data.message, data.success ? 'success' : 'error'); - if (data.success) { - loadDatasets(); // Recargar para actualizar conteos - updateStatus(); - } - }); - } -} - -// Variables para edición de variables -let currentEditingVariable = null; - -// Editar variable -function editVariable(name) { - if (!currentDatasetId) { - showMessage('No dataset selected', 'error'); - return; - } - - currentEditingVariable = name; - - // Obtener datos de la variable del dataset actual - const dataset = currentDatasets[currentDatasetId]; - if (dataset && dataset.variables && dataset.variables[name]) { - const variable = dataset.variables[name]; - const streamingVars = dataset.streaming_variables || []; - - // Crear objeto de variable con la misma estructura que la API - const variableData = { - name: name, - area: variable.area, - db: variable.db, - offset: variable.offset, - type: variable.type, - bit: variable.bit, - streaming: streamingVars.includes(name) - }; - - populateEditForm(variableData); - document.getElementById('edit-modal').style.display = 'block'; - } else { - showMessage('Variable not found in current dataset', 'error'); - } -} - -// Rellenar formulario de edición -function populateEditForm(variable) { - document.getElementById('edit-var-name').value = variable.name; - document.getElementById('edit-var-area').value = variable.area; - document.getElementById('edit-var-offset').value = variable.offset; - document.getElementById('edit-var-type').value = variable.type; - - if (variable.db) { - document.getElementById('edit-var-db').value = variable.db; - } - - if (variable.bit !== undefined) { - document.getElementById('edit-var-bit').value = variable.bit; - } - - // Actualizar visibilidad de campos según el área - toggleEditFields(); -} - -// Cerrar modal de edición -function closeEditModal() { - document.getElementById('edit-modal').style.display = 'none'; - currentEditingVariable = null; -} - -// Inicializar listeners para edición de variables -function initVariableEditListeners() { - // Manejar envío del formulario de edición - document.getElementById('edit-variable-form').addEventListener('submit', function (e) { - e.preventDefault(); - - if (!currentEditingVariable || !currentDatasetId) { - showMessage('No variable or dataset selected for editing', 'error'); - return; - } - - const area = document.getElementById('edit-var-area').value; - const newName = document.getElementById('edit-var-name').value; - - // Primero eliminar la variable antigua - fetch(`/api/datasets/${currentDatasetId}/variables/${currentEditingVariable}`, { - method: 'DELETE' - }) - .then(response => response.json()) - .then(deleteResult => { - if (deleteResult.success) { - // Luego añadir la variable actualizada - const data = { - name: newName, - area: area, - db: area === 'db' ? parseInt(document.getElementById('edit-var-db').value) : 1, - offset: parseInt(document.getElementById('edit-var-offset').value), - type: document.getElementById('edit-var-type').value, - streaming: false // Se restaurará abajo si estaba habilitado - }; - - // Añadir parámetro bit para áreas de bit - if (area === 'e' || area === 'a' || area === 'mb') { - data.bit = parseInt(document.getElementById('edit-var-bit').value); - } - - return fetch(`/api/datasets/${currentDatasetId}/variables`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(data) - }); - } else { - throw new Error(deleteResult.message); - } - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - showMessage('Variable updated successfully', 'success'); - closeEditModal(); - loadDatasets(); - updateStatus(); - } else { - showMessage(data.message, 'error'); - } - }) - .catch(error => { - showMessage(`Error updating variable: ${error}`, 'error'); - }); - }); - - // Cerrar modal al hacer clic fuera de él - window.onclick = function (event) { - const editModal = document.getElementById('edit-modal'); - const datasetModal = document.getElementById('dataset-modal'); - if (event.target === editModal) { - closeEditModal(); - } - if (event.target === datasetModal) { - datasetModal.style.display = 'none'; - } - } -} \ No newline at end of file diff --git a/web_test/PLC S7-315 Streamer & Logger_files/events.js.descargar b/web_test/PLC S7-315 Streamer & Logger_files/events.js.descargar deleted file mode 100644 index 2b2a410..0000000 --- a/web_test/PLC S7-315 Streamer & Logger_files/events.js.descargar +++ /dev/null @@ -1,168 +0,0 @@ -/** - * Gestión de eventos de la aplicación y log de eventos - */ - -// Refrescar log de eventos -function refreshEventLog() { - const limit = document.getElementById('log-limit').value; - - fetch(`/api/events?limit=${limit}`) - .then(response => response.json()) - .then(data => { - if (data.success) { - const logContainer = document.getElementById('events-log'); - const logStats = document.getElementById('log-stats'); - - // Limpiar entradas existentes - logContainer.innerHTML = ''; - - // Actualizar estadísticas - logStats.textContent = `Showing ${data.showing} of ${data.total_events} events`; - - // Añadir eventos (orden inverso para mostrar primero los más nuevos) - const events = data.events.reverse(); - - if (events.length === 0) { - logContainer.innerHTML = ` -
-
- 📋 System - ${new Date().toLocaleString('es-ES')} -
-
No events found
-
- `; - } else { - events.forEach(event => { - logContainer.appendChild(createLogEntry(event)); - }); - } - - // Auto-scroll al inicio para mostrar eventos más nuevos - logContainer.scrollTop = 0; - } else { - console.error('Error loading events:', data.error); - showMessage('Error loading events log', 'error'); - } - }) - .catch(error => { - console.error('Error fetching events:', error); - showMessage('Error fetching events log', 'error'); - }); -} - -// Crear entrada de log -function createLogEntry(event) { - const logEntry = document.createElement('div'); - logEntry.className = `log-entry log-${event.level}`; - - const hasDetails = event.details && Object.keys(event.details).length > 0; - - logEntry.innerHTML = ` -
- ${getEventIcon(event.event_type)} ${event.event_type.replace(/_/g, ' ').toUpperCase()} - ${formatTimestamp(event.timestamp)} -
-
${event.message}
- ${hasDetails ? `
${JSON.stringify(event.details, null, 2)}
` : ''} - `; - - return logEntry; -} - -// Limpiar vista de log -function clearLogView() { - const logContainer = document.getElementById('events-log'); - logContainer.innerHTML = ` -
-
- 🧹 System - ${new Date().toLocaleString('es-ES')} -
-
Log view cleared. Click refresh to reload events.
-
- `; - - const logStats = document.getElementById('log-stats'); - logStats.textContent = 'Log view cleared'; -} - -// Inicializar listeners para eventos -function initEventListeners() { - // Botones de control de log para el tab de events - const refreshBtn = document.getElementById('refresh-events-btn'); - const clearBtn = document.getElementById('clear-events-btn'); - - if (refreshBtn) { - refreshBtn.addEventListener('click', loadEvents); - } - - if (clearBtn) { - clearBtn.addEventListener('click', clearEventsView); - } -} - -// Función para cargar eventos en el tab de events -window.loadEvents = function() { - fetch('/api/events?limit=50') - .then(response => response.json()) - .then(data => { - if (data.success) { - const eventsContainer = document.getElementById('events-container'); - const eventsCount = document.getElementById('events-count'); - - // Limpiar contenedor - eventsContainer.innerHTML = ''; - - // Actualizar contador - eventsCount.textContent = data.showing || 0; - - // Añadir eventos (orden inverso para mostrar primero los más nuevos) - const events = data.events.reverse(); - - if (events.length === 0) { - eventsContainer.innerHTML = ` -
-
- 📋 System - ${new Date().toLocaleString('es-ES')} -
-
No events found
-
- `; - } else { - events.forEach(event => { - eventsContainer.appendChild(createLogEntry(event)); - }); - } - - // Auto-scroll al inicio para mostrar eventos más nuevos - eventsContainer.scrollTop = 0; - } else { - console.error('Error loading events:', data.error); - showMessage('Error loading events log', 'error'); - } - }) - .catch(error => { - console.error('Error fetching events:', error); - showMessage('Error fetching events log', 'error'); - }); -} - -// Función para limpiar vista de eventos -function clearEventsView() { - const eventsContainer = document.getElementById('events-container'); - const eventsCount = document.getElementById('events-count'); - - eventsContainer.innerHTML = ` -
-
- 🧹 System - ${new Date().toLocaleString('es-ES')} -
-
Events view cleared. Click refresh to reload events.
-
- `; - - eventsCount.textContent = '0'; -} \ No newline at end of file diff --git a/web_test/PLC S7-315 Streamer & Logger_files/main.js.descargar b/web_test/PLC S7-315 Streamer & Logger_files/main.js.descargar deleted file mode 100644 index 8f23ce6..0000000 --- a/web_test/PLC S7-315 Streamer & Logger_files/main.js.descargar +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Archivo principal que inicializa todos los componentes - */ - -// Inicializar la aplicación al cargar el documento -document.addEventListener('DOMContentLoaded', function () { - // Inicializar tema - loadTheme(); - - // Iniciar streaming de estado automáticamente - startStatusStreaming(); - - // Cargar datos iniciales - loadDatasets(); - updateStatus(); - loadCsvConfig(); - refreshEventLog(); - - // Inicializar listeners de eventos - initPlcListeners(); - initDatasetListeners(); - initVariableEditListeners(); - initStreamingListeners(); - initCsvListeners(); - initEventListeners(); - - // 🔑 NUEVO: Inicializar plotManager si existe - if (typeof PlotManager !== 'undefined' && !window.plotManager) { - window.plotManager = new PlotManager(); - console.log('📈 PlotManager initialized from main.js'); - } - - // Configurar actualizaciones periódicas como respaldo - setInterval(updateStatus, 30000); // Cada 30 segundos como respaldo - setInterval(refreshEventLog, 10000); // Cada 10 segundos - - // Inicializar visibilidad de campos en formularios - toggleFields(); -}); - -// Limpiar conexiones SSE cuando se descarga la página -window.addEventListener('beforeunload', function () { - if (variableEventSource) { - variableEventSource.close(); - } - if (statusEventSource) { - statusEventSource.close(); - } -}); \ No newline at end of file diff --git a/web_test/PLC S7-315 Streamer & Logger_files/pico.min.css b/web_test/PLC S7-315 Streamer & Logger_files/pico.min.css deleted file mode 100644 index e10ec26..0000000 --- a/web_test/PLC S7-315 Streamer & Logger_files/pico.min.css +++ /dev/null @@ -1,4 +0,0 @@ -@charset "UTF-8";/*! - * Pico CSS ✨ v2.1.1 (https://picocss.com) - * Copyright 2019-2025 - Licensed under MIT - */:host,:root{--pico-font-family-emoji:"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--pico-font-family-sans-serif:system-ui,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,Helvetica,Arial,"Helvetica Neue",sans-serif,var(--pico-font-family-emoji);--pico-font-family-monospace:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace,var(--pico-font-family-emoji);--pico-font-family:var(--pico-font-family-sans-serif);--pico-line-height:1.5;--pico-font-weight:400;--pico-font-size:100%;--pico-text-underline-offset:0.1rem;--pico-border-radius:0.25rem;--pico-border-width:0.0625rem;--pico-outline-width:0.125rem;--pico-transition:0.2s ease-in-out;--pico-spacing:1rem;--pico-typography-spacing-vertical:1rem;--pico-block-spacing-vertical:var(--pico-spacing);--pico-block-spacing-horizontal:var(--pico-spacing);--pico-grid-column-gap:var(--pico-spacing);--pico-grid-row-gap:var(--pico-spacing);--pico-form-element-spacing-vertical:0.75rem;--pico-form-element-spacing-horizontal:1rem;--pico-group-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-group-box-shadow-focus-with-button:0 0 0 var(--pico-outline-width) var(--pico-primary-focus);--pico-group-box-shadow-focus-with-input:0 0 0 0.0625rem var(--pico-form-element-border-color);--pico-modal-overlay-backdrop-filter:blur(0.375rem);--pico-nav-element-spacing-vertical:1rem;--pico-nav-element-spacing-horizontal:0.5rem;--pico-nav-link-spacing-vertical:0.5rem;--pico-nav-link-spacing-horizontal:0.5rem;--pico-nav-breadcrumb-divider:">";--pico-icon-checkbox:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");--pico-icon-minus:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E");--pico-icon-chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--pico-icon-date:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E");--pico-icon-time:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E");--pico-icon-search:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E");--pico-icon-close:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E");--pico-icon-loading:url("data:image/svg+xml,%3Csvg fill='none' height='24' width='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' %3E%3Cstyle%3E g %7B animation: rotate 2s linear infinite; transform-origin: center center; %7D circle %7B stroke-dasharray: 75,100; stroke-dashoffset: -5; animation: dash 1.5s ease-in-out infinite; stroke-linecap: round; %7D @keyframes rotate %7B 0%25 %7B transform: rotate(0deg); %7D 100%25 %7B transform: rotate(360deg); %7D %7D @keyframes dash %7B 0%25 %7B stroke-dasharray: 1,100; stroke-dashoffset: 0; %7D 50%25 %7B stroke-dasharray: 44.5,100; stroke-dashoffset: -17.5; %7D 100%25 %7B stroke-dasharray: 44.5,100; stroke-dashoffset: -62; %7D %7D %3C/style%3E%3Cg%3E%3Ccircle cx='12' cy='12' r='10' fill='none' stroke='rgb(136, 145, 164)' stroke-width='4' /%3E%3C/g%3E%3C/svg%3E")}@media (min-width:576px){:host,:root{--pico-font-size:106.25%}}@media (min-width:768px){:host,:root{--pico-font-size:112.5%}}@media (min-width:1024px){:host,:root{--pico-font-size:118.75%}}@media (min-width:1280px){:host,:root{--pico-font-size:125%}}@media (min-width:1536px){:host,:root{--pico-font-size:131.25%}}a{--pico-text-decoration:underline}a.contrast,a.secondary{--pico-text-decoration:underline}small{--pico-font-size:0.875em}h1,h2,h3,h4,h5,h6{--pico-font-weight:700}h1{--pico-font-size:2rem;--pico-line-height:1.125;--pico-typography-spacing-top:3rem}h2{--pico-font-size:1.75rem;--pico-line-height:1.15;--pico-typography-spacing-top:2.625rem}h3{--pico-font-size:1.5rem;--pico-line-height:1.175;--pico-typography-spacing-top:2.25rem}h4{--pico-font-size:1.25rem;--pico-line-height:1.2;--pico-typography-spacing-top:1.874rem}h5{--pico-font-size:1.125rem;--pico-line-height:1.225;--pico-typography-spacing-top:1.6875rem}h6{--pico-font-size:1rem;--pico-line-height:1.25;--pico-typography-spacing-top:1.5rem}tfoot td,tfoot th,thead td,thead th{--pico-font-weight:600;--pico-border-width:0.1875rem}code,kbd,pre,samp{--pico-font-family:var(--pico-font-family-monospace)}kbd{--pico-font-weight:bolder}:where(select,textarea),input:not([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]){--pico-outline-width:0.0625rem}[type=search]{--pico-border-radius:5rem}[type=checkbox],[type=radio]{--pico-border-width:0.125rem}[type=checkbox][role=switch]{--pico-border-width:0.1875rem}details.dropdown summary:not([role=button]){--pico-outline-width:0.0625rem}nav details.dropdown summary:focus-visible{--pico-outline-width:0.125rem}[role=search]{--pico-border-radius:5rem}[role=group]:has(button.secondary:focus,[type=submit].secondary:focus,[type=button].secondary:focus,[role=button].secondary:focus),[role=search]:has(button.secondary:focus,[type=submit].secondary:focus,[type=button].secondary:focus,[role=button].secondary:focus){--pico-group-box-shadow-focus-with-button:0 0 0 var(--pico-outline-width) var(--pico-secondary-focus)}[role=group]:has(button.contrast:focus,[type=submit].contrast:focus,[type=button].contrast:focus,[role=button].contrast:focus),[role=search]:has(button.contrast:focus,[type=submit].contrast:focus,[type=button].contrast:focus,[role=button].contrast:focus){--pico-group-box-shadow-focus-with-button:0 0 0 var(--pico-outline-width) var(--pico-contrast-focus)}[role=group] [role=button],[role=group] [type=button],[role=group] [type=submit],[role=group] button,[role=search] [role=button],[role=search] [type=button],[role=search] [type=submit],[role=search] button{--pico-form-element-spacing-horizontal:2rem}details summary[role=button]:not(.outline)::after{filter:brightness(0) invert(1)}[aria-busy=true]:not(input,select,textarea):is(button,[type=submit],[type=button],[type=reset],[role=button]):not(.outline)::before{filter:brightness(0) invert(1)}:host(:not([data-theme=dark])),:root:not([data-theme=dark]),[data-theme=light]{color-scheme:light;--pico-background-color:#fff;--pico-color:#373c44;--pico-text-selection-color:rgba(2, 154, 232, 0.25);--pico-muted-color:#646b79;--pico-muted-border-color:rgb(231, 234, 239.5);--pico-primary:#0172ad;--pico-primary-background:#0172ad;--pico-primary-border:var(--pico-primary-background);--pico-primary-underline:rgba(1, 114, 173, 0.5);--pico-primary-hover:#015887;--pico-primary-hover-background:#02659a;--pico-primary-hover-border:var(--pico-primary-hover-background);--pico-primary-hover-underline:var(--pico-primary-hover);--pico-primary-focus:rgba(2, 154, 232, 0.5);--pico-primary-inverse:#fff;--pico-secondary:#5d6b89;--pico-secondary-background:#525f7a;--pico-secondary-border:var(--pico-secondary-background);--pico-secondary-underline:rgba(93, 107, 137, 0.5);--pico-secondary-hover:#48536b;--pico-secondary-hover-background:#48536b;--pico-secondary-hover-border:var(--pico-secondary-hover-background);--pico-secondary-hover-underline:var(--pico-secondary-hover);--pico-secondary-focus:rgba(93, 107, 137, 0.25);--pico-secondary-inverse:#fff;--pico-contrast:#181c25;--pico-contrast-background:#181c25;--pico-contrast-border:var(--pico-contrast-background);--pico-contrast-underline:rgba(24, 28, 37, 0.5);--pico-contrast-hover:#000;--pico-contrast-hover-background:#000;--pico-contrast-hover-border:var(--pico-contrast-hover-background);--pico-contrast-hover-underline:var(--pico-secondary-hover);--pico-contrast-focus:rgba(93, 107, 137, 0.25);--pico-contrast-inverse:#fff;--pico-box-shadow:0.0145rem 0.029rem 0.174rem rgba(129, 145, 181, 0.01698),0.0335rem 0.067rem 0.402rem rgba(129, 145, 181, 0.024),0.0625rem 0.125rem 0.75rem rgba(129, 145, 181, 0.03),0.1125rem 0.225rem 1.35rem rgba(129, 145, 181, 0.036),0.2085rem 0.417rem 2.502rem rgba(129, 145, 181, 0.04302),0.5rem 1rem 6rem rgba(129, 145, 181, 0.06),0 0 0 0.0625rem rgba(129, 145, 181, 0.015);--pico-h1-color:#2d3138;--pico-h2-color:#373c44;--pico-h3-color:#424751;--pico-h4-color:#4d535e;--pico-h5-color:#5c6370;--pico-h6-color:#646b79;--pico-mark-background-color:rgb(252.5, 230.5, 191.5);--pico-mark-color:#0f1114;--pico-ins-color:rgb(28.5, 105.5, 84);--pico-del-color:rgb(136, 56.5, 53);--pico-blockquote-border-color:var(--pico-muted-border-color);--pico-blockquote-footer-color:var(--pico-muted-color);--pico-button-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-button-hover-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-table-border-color:var(--pico-muted-border-color);--pico-table-row-stripped-background-color:rgba(111, 120, 135, 0.0375);--pico-code-background-color:rgb(243, 244.5, 246.75);--pico-code-color:#646b79;--pico-code-kbd-background-color:var(--pico-color);--pico-code-kbd-color:var(--pico-background-color);--pico-form-element-background-color:rgb(251, 251.5, 252.25);--pico-form-element-selected-background-color:#dfe3eb;--pico-form-element-border-color:#cfd5e2;--pico-form-element-color:#23262c;--pico-form-element-placeholder-color:var(--pico-muted-color);--pico-form-element-active-background-color:#fff;--pico-form-element-active-border-color:var(--pico-primary-border);--pico-form-element-focus-color:var(--pico-primary-border);--pico-form-element-disabled-opacity:0.5;--pico-form-element-invalid-border-color:rgb(183.5, 105.5, 106.5);--pico-form-element-invalid-active-border-color:rgb(200.25, 79.25, 72.25);--pico-form-element-invalid-focus-color:var(--pico-form-element-invalid-active-border-color);--pico-form-element-valid-border-color:rgb(76, 154.5, 137.5);--pico-form-element-valid-active-border-color:rgb(39, 152.75, 118.75);--pico-form-element-valid-focus-color:var(--pico-form-element-valid-active-border-color);--pico-switch-background-color:#bfc7d9;--pico-switch-checked-background-color:var(--pico-primary-background);--pico-switch-color:#fff;--pico-switch-thumb-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-range-border-color:#dfe3eb;--pico-range-active-border-color:#bfc7d9;--pico-range-thumb-border-color:var(--pico-background-color);--pico-range-thumb-color:var(--pico-secondary-background);--pico-range-thumb-active-color:var(--pico-primary-background);--pico-accordion-border-color:var(--pico-muted-border-color);--pico-accordion-active-summary-color:var(--pico-primary-hover);--pico-accordion-close-summary-color:var(--pico-color);--pico-accordion-open-summary-color:var(--pico-muted-color);--pico-card-background-color:var(--pico-background-color);--pico-card-border-color:var(--pico-muted-border-color);--pico-card-box-shadow:var(--pico-box-shadow);--pico-card-sectioning-background-color:rgb(251, 251.5, 252.25);--pico-dropdown-background-color:#fff;--pico-dropdown-border-color:#eff1f4;--pico-dropdown-box-shadow:var(--pico-box-shadow);--pico-dropdown-color:var(--pico-color);--pico-dropdown-hover-background-color:#eff1f4;--pico-loading-spinner-opacity:0.5;--pico-modal-overlay-background-color:rgba(232, 234, 237, 0.75);--pico-progress-background-color:#dfe3eb;--pico-progress-color:var(--pico-primary-background);--pico-tooltip-background-color:var(--pico-contrast-background);--pico-tooltip-color:var(--pico-contrast-inverse);--pico-icon-valid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(76, 154.5, 137.5)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");--pico-icon-invalid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(200.25, 79.25, 72.25)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E")}:host(:not([data-theme=dark])) input:is([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]),:root:not([data-theme=dark]) input:is([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]),[data-theme=light] input:is([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]){--pico-form-element-focus-color:var(--pico-primary-focus)}@media only screen and (prefers-color-scheme:dark){:host(:not([data-theme])),:root:not([data-theme]){color-scheme:dark;--pico-background-color:rgb(19, 22.5, 30.5);--pico-color:#c2c7d0;--pico-text-selection-color:rgba(1, 170, 255, 0.1875);--pico-muted-color:#7b8495;--pico-muted-border-color:#202632;--pico-primary:#01aaff;--pico-primary-background:#0172ad;--pico-primary-border:var(--pico-primary-background);--pico-primary-underline:rgba(1, 170, 255, 0.5);--pico-primary-hover:#79c0ff;--pico-primary-hover-background:#017fc0;--pico-primary-hover-border:var(--pico-primary-hover-background);--pico-primary-hover-underline:var(--pico-primary-hover);--pico-primary-focus:rgba(1, 170, 255, 0.375);--pico-primary-inverse:#fff;--pico-secondary:#969eaf;--pico-secondary-background:#525f7a;--pico-secondary-border:var(--pico-secondary-background);--pico-secondary-underline:rgba(150, 158, 175, 0.5);--pico-secondary-hover:#b3b9c5;--pico-secondary-hover-background:#5d6b89;--pico-secondary-hover-border:var(--pico-secondary-hover-background);--pico-secondary-hover-underline:var(--pico-secondary-hover);--pico-secondary-focus:rgba(144, 158, 190, 0.25);--pico-secondary-inverse:#fff;--pico-contrast:#dfe3eb;--pico-contrast-background:#eff1f4;--pico-contrast-border:var(--pico-contrast-background);--pico-contrast-underline:rgba(223, 227, 235, 0.5);--pico-contrast-hover:#fff;--pico-contrast-hover-background:#fff;--pico-contrast-hover-border:var(--pico-contrast-hover-background);--pico-contrast-hover-underline:var(--pico-contrast-hover);--pico-contrast-focus:rgba(207, 213, 226, 0.25);--pico-contrast-inverse:#000;--pico-box-shadow:0.0145rem 0.029rem 0.174rem rgba(7, 8.5, 12, 0.01698),0.0335rem 0.067rem 0.402rem rgba(7, 8.5, 12, 0.024),0.0625rem 0.125rem 0.75rem rgba(7, 8.5, 12, 0.03),0.1125rem 0.225rem 1.35rem rgba(7, 8.5, 12, 0.036),0.2085rem 0.417rem 2.502rem rgba(7, 8.5, 12, 0.04302),0.5rem 1rem 6rem rgba(7, 8.5, 12, 0.06),0 0 0 0.0625rem rgba(7, 8.5, 12, 0.015);--pico-h1-color:#f0f1f3;--pico-h2-color:#e0e3e7;--pico-h3-color:#c2c7d0;--pico-h4-color:#b3b9c5;--pico-h5-color:#a4acba;--pico-h6-color:#8891a4;--pico-mark-background-color:#014063;--pico-mark-color:#fff;--pico-ins-color:#62af9a;--pico-del-color:rgb(205.5, 126, 123);--pico-blockquote-border-color:var(--pico-muted-border-color);--pico-blockquote-footer-color:var(--pico-muted-color);--pico-button-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-button-hover-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-table-border-color:var(--pico-muted-border-color);--pico-table-row-stripped-background-color:rgba(111, 120, 135, 0.0375);--pico-code-background-color:rgb(26, 30.5, 40.25);--pico-code-color:#8891a4;--pico-code-kbd-background-color:var(--pico-color);--pico-code-kbd-color:var(--pico-background-color);--pico-form-element-background-color:rgb(28, 33, 43.5);--pico-form-element-selected-background-color:#2a3140;--pico-form-element-border-color:#2a3140;--pico-form-element-color:#e0e3e7;--pico-form-element-placeholder-color:#8891a4;--pico-form-element-active-background-color:rgb(26, 30.5, 40.25);--pico-form-element-active-border-color:var(--pico-primary-border);--pico-form-element-focus-color:var(--pico-primary-border);--pico-form-element-disabled-opacity:0.5;--pico-form-element-invalid-border-color:rgb(149.5, 74, 80);--pico-form-element-invalid-active-border-color:rgb(183.25, 63.5, 59);--pico-form-element-invalid-focus-color:var(--pico-form-element-invalid-active-border-color);--pico-form-element-valid-border-color:#2a7b6f;--pico-form-element-valid-active-border-color:rgb(22, 137, 105.5);--pico-form-element-valid-focus-color:var(--pico-form-element-valid-active-border-color);--pico-switch-background-color:#333c4e;--pico-switch-checked-background-color:var(--pico-primary-background);--pico-switch-color:#fff;--pico-switch-thumb-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-range-border-color:#202632;--pico-range-active-border-color:#2a3140;--pico-range-thumb-border-color:var(--pico-background-color);--pico-range-thumb-color:var(--pico-secondary-background);--pico-range-thumb-active-color:var(--pico-primary-background);--pico-accordion-border-color:var(--pico-muted-border-color);--pico-accordion-active-summary-color:var(--pico-primary-hover);--pico-accordion-close-summary-color:var(--pico-color);--pico-accordion-open-summary-color:var(--pico-muted-color);--pico-card-background-color:#181c25;--pico-card-border-color:var(--pico-card-background-color);--pico-card-box-shadow:var(--pico-box-shadow);--pico-card-sectioning-background-color:rgb(26, 30.5, 40.25);--pico-dropdown-background-color:#181c25;--pico-dropdown-border-color:#202632;--pico-dropdown-box-shadow:var(--pico-box-shadow);--pico-dropdown-color:var(--pico-color);--pico-dropdown-hover-background-color:#202632;--pico-loading-spinner-opacity:0.5;--pico-modal-overlay-background-color:rgba(7.5, 8.5, 10, 0.75);--pico-progress-background-color:#202632;--pico-progress-color:var(--pico-primary-background);--pico-tooltip-background-color:var(--pico-contrast-background);--pico-tooltip-color:var(--pico-contrast-inverse);--pico-icon-valid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(42, 123, 111)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");--pico-icon-invalid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(149.5, 74, 80)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E")}:host(:not([data-theme])) input:is([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]),:root:not([data-theme]) input:is([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]){--pico-form-element-focus-color:var(--pico-primary-focus)}:host(:not([data-theme])) details summary[role=button].contrast:not(.outline)::after,:root:not([data-theme]) details summary[role=button].contrast:not(.outline)::after{filter:brightness(0)}:host(:not([data-theme])) [aria-busy=true]:not(input,select,textarea).contrast:is(button,[type=submit],[type=button],[type=reset],[role=button]):not(.outline)::before,:root:not([data-theme]) [aria-busy=true]:not(input,select,textarea).contrast:is(button,[type=submit],[type=button],[type=reset],[role=button]):not(.outline)::before{filter:brightness(0)}}[data-theme=dark]{color-scheme:dark;--pico-background-color:rgb(19, 22.5, 30.5);--pico-color:#c2c7d0;--pico-text-selection-color:rgba(1, 170, 255, 0.1875);--pico-muted-color:#7b8495;--pico-muted-border-color:#202632;--pico-primary:#01aaff;--pico-primary-background:#0172ad;--pico-primary-border:var(--pico-primary-background);--pico-primary-underline:rgba(1, 170, 255, 0.5);--pico-primary-hover:#79c0ff;--pico-primary-hover-background:#017fc0;--pico-primary-hover-border:var(--pico-primary-hover-background);--pico-primary-hover-underline:var(--pico-primary-hover);--pico-primary-focus:rgba(1, 170, 255, 0.375);--pico-primary-inverse:#fff;--pico-secondary:#969eaf;--pico-secondary-background:#525f7a;--pico-secondary-border:var(--pico-secondary-background);--pico-secondary-underline:rgba(150, 158, 175, 0.5);--pico-secondary-hover:#b3b9c5;--pico-secondary-hover-background:#5d6b89;--pico-secondary-hover-border:var(--pico-secondary-hover-background);--pico-secondary-hover-underline:var(--pico-secondary-hover);--pico-secondary-focus:rgba(144, 158, 190, 0.25);--pico-secondary-inverse:#fff;--pico-contrast:#dfe3eb;--pico-contrast-background:#eff1f4;--pico-contrast-border:var(--pico-contrast-background);--pico-contrast-underline:rgba(223, 227, 235, 0.5);--pico-contrast-hover:#fff;--pico-contrast-hover-background:#fff;--pico-contrast-hover-border:var(--pico-contrast-hover-background);--pico-contrast-hover-underline:var(--pico-contrast-hover);--pico-contrast-focus:rgba(207, 213, 226, 0.25);--pico-contrast-inverse:#000;--pico-box-shadow:0.0145rem 0.029rem 0.174rem rgba(7, 8.5, 12, 0.01698),0.0335rem 0.067rem 0.402rem rgba(7, 8.5, 12, 0.024),0.0625rem 0.125rem 0.75rem rgba(7, 8.5, 12, 0.03),0.1125rem 0.225rem 1.35rem rgba(7, 8.5, 12, 0.036),0.2085rem 0.417rem 2.502rem rgba(7, 8.5, 12, 0.04302),0.5rem 1rem 6rem rgba(7, 8.5, 12, 0.06),0 0 0 0.0625rem rgba(7, 8.5, 12, 0.015);--pico-h1-color:#f0f1f3;--pico-h2-color:#e0e3e7;--pico-h3-color:#c2c7d0;--pico-h4-color:#b3b9c5;--pico-h5-color:#a4acba;--pico-h6-color:#8891a4;--pico-mark-background-color:#014063;--pico-mark-color:#fff;--pico-ins-color:#62af9a;--pico-del-color:rgb(205.5, 126, 123);--pico-blockquote-border-color:var(--pico-muted-border-color);--pico-blockquote-footer-color:var(--pico-muted-color);--pico-button-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-button-hover-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-table-border-color:var(--pico-muted-border-color);--pico-table-row-stripped-background-color:rgba(111, 120, 135, 0.0375);--pico-code-background-color:rgb(26, 30.5, 40.25);--pico-code-color:#8891a4;--pico-code-kbd-background-color:var(--pico-color);--pico-code-kbd-color:var(--pico-background-color);--pico-form-element-background-color:rgb(28, 33, 43.5);--pico-form-element-selected-background-color:#2a3140;--pico-form-element-border-color:#2a3140;--pico-form-element-color:#e0e3e7;--pico-form-element-placeholder-color:#8891a4;--pico-form-element-active-background-color:rgb(26, 30.5, 40.25);--pico-form-element-active-border-color:var(--pico-primary-border);--pico-form-element-focus-color:var(--pico-primary-border);--pico-form-element-disabled-opacity:0.5;--pico-form-element-invalid-border-color:rgb(149.5, 74, 80);--pico-form-element-invalid-active-border-color:rgb(183.25, 63.5, 59);--pico-form-element-invalid-focus-color:var(--pico-form-element-invalid-active-border-color);--pico-form-element-valid-border-color:#2a7b6f;--pico-form-element-valid-active-border-color:rgb(22, 137, 105.5);--pico-form-element-valid-focus-color:var(--pico-form-element-valid-active-border-color);--pico-switch-background-color:#333c4e;--pico-switch-checked-background-color:var(--pico-primary-background);--pico-switch-color:#fff;--pico-switch-thumb-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-range-border-color:#202632;--pico-range-active-border-color:#2a3140;--pico-range-thumb-border-color:var(--pico-background-color);--pico-range-thumb-color:var(--pico-secondary-background);--pico-range-thumb-active-color:var(--pico-primary-background);--pico-accordion-border-color:var(--pico-muted-border-color);--pico-accordion-active-summary-color:var(--pico-primary-hover);--pico-accordion-close-summary-color:var(--pico-color);--pico-accordion-open-summary-color:var(--pico-muted-color);--pico-card-background-color:#181c25;--pico-card-border-color:var(--pico-card-background-color);--pico-card-box-shadow:var(--pico-box-shadow);--pico-card-sectioning-background-color:rgb(26, 30.5, 40.25);--pico-dropdown-background-color:#181c25;--pico-dropdown-border-color:#202632;--pico-dropdown-box-shadow:var(--pico-box-shadow);--pico-dropdown-color:var(--pico-color);--pico-dropdown-hover-background-color:#202632;--pico-loading-spinner-opacity:0.5;--pico-modal-overlay-background-color:rgba(7.5, 8.5, 10, 0.75);--pico-progress-background-color:#202632;--pico-progress-color:var(--pico-primary-background);--pico-tooltip-background-color:var(--pico-contrast-background);--pico-tooltip-color:var(--pico-contrast-inverse);--pico-icon-valid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(42, 123, 111)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");--pico-icon-invalid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(149.5, 74, 80)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E")}[data-theme=dark] input:is([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]){--pico-form-element-focus-color:var(--pico-primary-focus)}[data-theme=dark] details summary[role=button].contrast:not(.outline)::after{filter:brightness(0)}[data-theme=dark] [aria-busy=true]:not(input,select,textarea).contrast:is(button,[type=submit],[type=button],[type=reset],[role=button]):not(.outline)::before{filter:brightness(0)}[type=checkbox],[type=radio],[type=range],progress{accent-color:var(--pico-primary)}*,::after,::before{box-sizing:border-box;background-repeat:no-repeat}::after,::before{text-decoration:inherit;vertical-align:inherit}:where(:host),:where(:root){-webkit-tap-highlight-color:transparent;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;background-color:var(--pico-background-color);color:var(--pico-color);font-weight:var(--pico-font-weight);font-size:var(--pico-font-size);line-height:var(--pico-line-height);font-family:var(--pico-font-family);text-underline-offset:var(--pico-text-underline-offset);text-rendering:optimizeLegibility;overflow-wrap:break-word;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{width:100%;margin:0}main{display:block}body>footer,body>header,body>main{padding-block:var(--pico-block-spacing-vertical)}section{margin-bottom:var(--pico-block-spacing-vertical)}.container,.container-fluid{width:100%;margin-right:auto;margin-left:auto;padding-right:var(--pico-spacing);padding-left:var(--pico-spacing)}@media (min-width:576px){.container{max-width:510px;padding-right:0;padding-left:0}}@media (min-width:768px){.container{max-width:700px}}@media (min-width:1024px){.container{max-width:950px}}@media (min-width:1280px){.container{max-width:1200px}}@media (min-width:1536px){.container{max-width:1450px}}.grid{grid-column-gap:var(--pico-grid-column-gap);grid-row-gap:var(--pico-grid-row-gap);display:grid;grid-template-columns:1fr}@media (min-width:768px){.grid{grid-template-columns:repeat(auto-fit,minmax(0%,1fr))}}.grid>*{min-width:0}.overflow-auto{overflow:auto}b,strong{font-weight:bolder}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}address,blockquote,dl,ol,p,pre,table,ul{margin-top:0;margin-bottom:var(--pico-typography-spacing-vertical);color:var(--pico-color);font-style:normal;font-weight:var(--pico-font-weight)}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:var(--pico-typography-spacing-vertical);color:var(--pico-color);font-weight:var(--pico-font-weight);font-size:var(--pico-font-size);line-height:var(--pico-line-height);font-family:var(--pico-font-family)}h1{--pico-color:var(--pico-h1-color)}h2{--pico-color:var(--pico-h2-color)}h3{--pico-color:var(--pico-h3-color)}h4{--pico-color:var(--pico-h4-color)}h5{--pico-color:var(--pico-h5-color)}h6{--pico-color:var(--pico-h6-color)}:where(article,address,blockquote,dl,figure,form,ol,p,pre,table,ul)~:is(h1,h2,h3,h4,h5,h6){margin-top:var(--pico-typography-spacing-top)}p{margin-bottom:var(--pico-typography-spacing-vertical)}hgroup{margin-bottom:var(--pico-typography-spacing-vertical)}hgroup>*{margin-top:0;margin-bottom:0}hgroup>:not(:first-child):last-child{--pico-color:var(--pico-muted-color);--pico-font-weight:unset;font-size:1rem}:where(ol,ul) li{margin-bottom:calc(var(--pico-typography-spacing-vertical) * .25)}:where(dl,ol,ul) :where(dl,ol,ul){margin:0;margin-top:calc(var(--pico-typography-spacing-vertical) * .25)}ul li{list-style:square}mark{padding:.125rem .25rem;background-color:var(--pico-mark-background-color);color:var(--pico-mark-color);vertical-align:baseline}blockquote{display:block;margin:var(--pico-typography-spacing-vertical) 0;padding:var(--pico-spacing);border-right:none;border-left:.25rem solid var(--pico-blockquote-border-color);border-inline-start:0.25rem solid var(--pico-blockquote-border-color);border-inline-end:none}blockquote footer{margin-top:calc(var(--pico-typography-spacing-vertical) * .5);color:var(--pico-blockquote-footer-color)}abbr[title]{border-bottom:1px dotted;text-decoration:none;cursor:help}ins{color:var(--pico-ins-color);text-decoration:none}del{color:var(--pico-del-color)}::-moz-selection{background-color:var(--pico-text-selection-color)}::selection{background-color:var(--pico-text-selection-color)}:where(a:not([role=button])),[role=link]{--pico-color:var(--pico-primary);--pico-background-color:transparent;--pico-underline:var(--pico-primary-underline);outline:0;background-color:var(--pico-background-color);color:var(--pico-color);-webkit-text-decoration:var(--pico-text-decoration);text-decoration:var(--pico-text-decoration);text-decoration-color:var(--pico-underline);text-underline-offset:0.125em;transition:background-color var(--pico-transition),color var(--pico-transition),box-shadow var(--pico-transition),-webkit-text-decoration var(--pico-transition);transition:background-color var(--pico-transition),color var(--pico-transition),text-decoration var(--pico-transition),box-shadow var(--pico-transition);transition:background-color var(--pico-transition),color var(--pico-transition),text-decoration var(--pico-transition),box-shadow var(--pico-transition),-webkit-text-decoration var(--pico-transition)}:where(a:not([role=button])):is([aria-current]:not([aria-current=false]),:hover,:active,:focus),[role=link]:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){--pico-color:var(--pico-primary-hover);--pico-underline:var(--pico-primary-hover-underline);--pico-text-decoration:underline}:where(a:not([role=button])):focus-visible,[role=link]:focus-visible{box-shadow:0 0 0 var(--pico-outline-width) var(--pico-primary-focus)}:where(a:not([role=button])).secondary,[role=link].secondary{--pico-color:var(--pico-secondary);--pico-underline:var(--pico-secondary-underline)}:where(a:not([role=button])).secondary:is([aria-current]:not([aria-current=false]),:hover,:active,:focus),[role=link].secondary:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){--pico-color:var(--pico-secondary-hover);--pico-underline:var(--pico-secondary-hover-underline)}:where(a:not([role=button])).contrast,[role=link].contrast{--pico-color:var(--pico-contrast);--pico-underline:var(--pico-contrast-underline)}:where(a:not([role=button])).contrast:is([aria-current]:not([aria-current=false]),:hover,:active,:focus),[role=link].contrast:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){--pico-color:var(--pico-contrast-hover);--pico-underline:var(--pico-contrast-hover-underline)}a[role=button]{display:inline-block}button{margin:0;overflow:visible;font-family:inherit;text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[role=button],[type=button],[type=file]::file-selector-button,[type=reset],[type=submit],button{--pico-background-color:var(--pico-primary-background);--pico-border-color:var(--pico-primary-border);--pico-color:var(--pico-primary-inverse);--pico-box-shadow:var(--pico-button-box-shadow, 0 0 0 rgba(0, 0, 0, 0));padding:var(--pico-form-element-spacing-vertical) var(--pico-form-element-spacing-horizontal);border:var(--pico-border-width) solid var(--pico-border-color);border-radius:var(--pico-border-radius);outline:0;background-color:var(--pico-background-color);box-shadow:var(--pico-box-shadow);color:var(--pico-color);font-weight:var(--pico-font-weight);font-size:1rem;line-height:var(--pico-line-height);text-align:center;text-decoration:none;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;transition:background-color var(--pico-transition),border-color var(--pico-transition),color var(--pico-transition),box-shadow var(--pico-transition)}[role=button]:is(:hover,:active,:focus),[role=button]:is([aria-current]:not([aria-current=false])),[type=button]:is(:hover,:active,:focus),[type=button]:is([aria-current]:not([aria-current=false])),[type=file]::file-selector-button:is(:hover,:active,:focus),[type=file]::file-selector-button:is([aria-current]:not([aria-current=false])),[type=reset]:is(:hover,:active,:focus),[type=reset]:is([aria-current]:not([aria-current=false])),[type=submit]:is(:hover,:active,:focus),[type=submit]:is([aria-current]:not([aria-current=false])),button:is(:hover,:active,:focus),button:is([aria-current]:not([aria-current=false])){--pico-background-color:var(--pico-primary-hover-background);--pico-border-color:var(--pico-primary-hover-border);--pico-box-shadow:var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0));--pico-color:var(--pico-primary-inverse)}[role=button]:focus,[role=button]:is([aria-current]:not([aria-current=false])):focus,[type=button]:focus,[type=button]:is([aria-current]:not([aria-current=false])):focus,[type=file]::file-selector-button:focus,[type=file]::file-selector-button:is([aria-current]:not([aria-current=false])):focus,[type=reset]:focus,[type=reset]:is([aria-current]:not([aria-current=false])):focus,[type=submit]:focus,[type=submit]:is([aria-current]:not([aria-current=false])):focus,button:focus,button:is([aria-current]:not([aria-current=false])):focus{--pico-box-shadow:var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),0 0 0 var(--pico-outline-width) var(--pico-primary-focus)}[type=button],[type=reset],[type=submit]{margin-bottom:var(--pico-spacing)}:is(button,[type=submit],[type=button],[role=button]).secondary,[type=file]::file-selector-button,[type=reset]{--pico-background-color:var(--pico-secondary-background);--pico-border-color:var(--pico-secondary-border);--pico-color:var(--pico-secondary-inverse);cursor:pointer}:is(button,[type=submit],[type=button],[role=button]).secondary:is([aria-current]:not([aria-current=false]),:hover,:active,:focus),[type=file]::file-selector-button:is([aria-current]:not([aria-current=false]),:hover,:active,:focus),[type=reset]:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){--pico-background-color:var(--pico-secondary-hover-background);--pico-border-color:var(--pico-secondary-hover-border);--pico-color:var(--pico-secondary-inverse)}:is(button,[type=submit],[type=button],[role=button]).secondary:focus,:is(button,[type=submit],[type=button],[role=button]).secondary:is([aria-current]:not([aria-current=false])):focus,[type=file]::file-selector-button:focus,[type=file]::file-selector-button:is([aria-current]:not([aria-current=false])):focus,[type=reset]:focus,[type=reset]:is([aria-current]:not([aria-current=false])):focus{--pico-box-shadow:var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),0 0 0 var(--pico-outline-width) var(--pico-secondary-focus)}:is(button,[type=submit],[type=button],[role=button]).contrast{--pico-background-color:var(--pico-contrast-background);--pico-border-color:var(--pico-contrast-border);--pico-color:var(--pico-contrast-inverse)}:is(button,[type=submit],[type=button],[role=button]).contrast:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){--pico-background-color:var(--pico-contrast-hover-background);--pico-border-color:var(--pico-contrast-hover-border);--pico-color:var(--pico-contrast-inverse)}:is(button,[type=submit],[type=button],[role=button]).contrast:focus,:is(button,[type=submit],[type=button],[role=button]).contrast:is([aria-current]:not([aria-current=false])):focus{--pico-box-shadow:var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),0 0 0 var(--pico-outline-width) var(--pico-contrast-focus)}:is(button,[type=submit],[type=button],[role=button]).outline,[type=reset].outline{--pico-background-color:transparent;--pico-color:var(--pico-primary);--pico-border-color:var(--pico-primary)}:is(button,[type=submit],[type=button],[role=button]).outline:is([aria-current]:not([aria-current=false]),:hover,:active,:focus),[type=reset].outline:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){--pico-background-color:transparent;--pico-color:var(--pico-primary-hover);--pico-border-color:var(--pico-primary-hover)}:is(button,[type=submit],[type=button],[role=button]).outline.secondary,[type=reset].outline{--pico-color:var(--pico-secondary);--pico-border-color:var(--pico-secondary)}:is(button,[type=submit],[type=button],[role=button]).outline.secondary:is([aria-current]:not([aria-current=false]),:hover,:active,:focus),[type=reset].outline:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){--pico-color:var(--pico-secondary-hover);--pico-border-color:var(--pico-secondary-hover)}:is(button,[type=submit],[type=button],[role=button]).outline.contrast{--pico-color:var(--pico-contrast);--pico-border-color:var(--pico-contrast)}:is(button,[type=submit],[type=button],[role=button]).outline.contrast:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){--pico-color:var(--pico-contrast-hover);--pico-border-color:var(--pico-contrast-hover)}:where(button,[type=submit],[type=reset],[type=button],[role=button])[disabled],:where(fieldset[disabled]) :is(button,[type=submit],[type=button],[type=reset],[role=button]){opacity:.5;pointer-events:none}:where(table){width:100%;border-collapse:collapse;border-spacing:0;text-indent:0}td,th{padding:calc(var(--pico-spacing)/ 2) var(--pico-spacing);border-bottom:var(--pico-border-width) solid var(--pico-table-border-color);background-color:var(--pico-background-color);color:var(--pico-color);font-weight:var(--pico-font-weight);text-align:left;text-align:start}tfoot td,tfoot th{border-top:var(--pico-border-width) solid var(--pico-table-border-color);border-bottom:0}table.striped tbody tr:nth-child(odd) td,table.striped tbody tr:nth-child(odd) th{background-color:var(--pico-table-row-stripped-background-color)}:where(audio,canvas,iframe,img,svg,video){vertical-align:middle}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}:where(iframe){border-style:none}img{max-width:100%;height:auto;border-style:none}:where(svg:not([fill])){fill:currentColor}svg:not(:host),svg:not(:root){overflow:hidden}code,kbd,pre,samp{font-size:.875em;font-family:var(--pico-font-family)}pre code,pre samp{font-size:inherit;font-family:inherit}pre{-ms-overflow-style:scrollbar;overflow:auto}code,kbd,pre,samp{border-radius:var(--pico-border-radius);background:var(--pico-code-background-color);color:var(--pico-code-color);font-weight:var(--pico-font-weight);line-height:initial}code,kbd,samp{display:inline-block;padding:.375rem}pre{display:block;margin-bottom:var(--pico-spacing);overflow-x:auto}pre>code,pre>samp{display:block;padding:var(--pico-spacing);background:0 0;line-height:var(--pico-line-height)}kbd{background-color:var(--pico-code-kbd-background-color);color:var(--pico-code-kbd-color);vertical-align:baseline}figure{display:block;margin:0;padding:0}figure figcaption{padding:calc(var(--pico-spacing) * .5) 0;color:var(--pico-muted-color)}hr{height:0;margin:var(--pico-typography-spacing-vertical) 0;border:0;border-top:1px solid var(--pico-muted-border-color);color:inherit}[hidden],template{display:none!important}canvas{display:inline-block}input,optgroup,select,textarea{margin:0;font-size:1rem;line-height:var(--pico-line-height);font-family:inherit;letter-spacing:inherit}input{overflow:visible}select{text-transform:none}legend{max-width:100%;padding:0;color:inherit;white-space:normal}textarea{overflow:auto}[type=checkbox],[type=radio]{padding:0}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}::-moz-focus-inner{padding:0;border-style:none}:-moz-focusring{outline:0}:-moz-ui-invalid{box-shadow:none}::-ms-expand{display:none}[type=file],[type=range]{padding:0;border-width:0}input:not([type=checkbox],[type=radio],[type=range]){height:calc(1rem * var(--pico-line-height) + var(--pico-form-element-spacing-vertical) * 2 + var(--pico-border-width) * 2)}fieldset{width:100%;margin:0;margin-bottom:var(--pico-spacing);padding:0;border:0}fieldset legend,label{display:block;margin-bottom:calc(var(--pico-spacing) * .375);color:var(--pico-color);font-weight:var(--pico-form-label-font-weight,var(--pico-font-weight))}fieldset legend{margin-bottom:calc(var(--pico-spacing) * .5)}button[type=submit],input:not([type=checkbox],[type=radio]),select,textarea{width:100%}input:not([type=checkbox],[type=radio],[type=range],[type=file]),select,textarea{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:var(--pico-form-element-spacing-vertical) var(--pico-form-element-spacing-horizontal)}input,select,textarea{--pico-background-color:var(--pico-form-element-background-color);--pico-border-color:var(--pico-form-element-border-color);--pico-color:var(--pico-form-element-color);--pico-box-shadow:none;border:var(--pico-border-width) solid var(--pico-border-color);border-radius:var(--pico-border-radius);outline:0;background-color:var(--pico-background-color);box-shadow:var(--pico-box-shadow);color:var(--pico-color);font-weight:var(--pico-font-weight);transition:background-color var(--pico-transition),border-color var(--pico-transition),color var(--pico-transition),box-shadow var(--pico-transition)}:where(select,textarea):not([readonly]):is(:active,:focus),input:not([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[readonly]):is(:active,:focus){--pico-background-color:var(--pico-form-element-active-background-color)}:where(select,textarea):not([readonly]):is(:active,:focus),input:not([type=submit],[type=button],[type=reset],[role=switch],[readonly]):is(:active,:focus){--pico-border-color:var(--pico-form-element-active-border-color)}:where(select,textarea):not([readonly]):focus,input:not([type=submit],[type=button],[type=reset],[type=range],[type=file],[readonly]):focus{--pico-box-shadow:0 0 0 var(--pico-outline-width) var(--pico-form-element-focus-color)}:where(fieldset[disabled]) :is(input:not([type=submit],[type=button],[type=reset]),select,textarea),input:not([type=submit],[type=button],[type=reset])[disabled],label[aria-disabled=true],select[disabled],textarea[disabled]{opacity:var(--pico-form-element-disabled-opacity);pointer-events:none}label[aria-disabled=true] input[disabled]{opacity:1}:where(input,select,textarea):not([type=checkbox],[type=radio],[type=date],[type=datetime-local],[type=month],[type=time],[type=week],[type=range])[aria-invalid]{padding-right:calc(var(--pico-form-element-spacing-horizontal) + 1.5rem)!important;padding-left:var(--pico-form-element-spacing-horizontal);padding-inline-start:var(--pico-form-element-spacing-horizontal)!important;padding-inline-end:calc(var(--pico-form-element-spacing-horizontal) + 1.5rem)!important;background-position:center right .75rem;background-size:1rem auto;background-repeat:no-repeat}:where(input,select,textarea):not([type=checkbox],[type=radio],[type=date],[type=datetime-local],[type=month],[type=time],[type=week],[type=range])[aria-invalid=false]:not(select){background-image:var(--pico-icon-valid)}:where(input,select,textarea):not([type=checkbox],[type=radio],[type=date],[type=datetime-local],[type=month],[type=time],[type=week],[type=range])[aria-invalid=true]:not(select){background-image:var(--pico-icon-invalid)}:where(input,select,textarea)[aria-invalid=false]{--pico-border-color:var(--pico-form-element-valid-border-color)}:where(input,select,textarea)[aria-invalid=false]:is(:active,:focus){--pico-border-color:var(--pico-form-element-valid-active-border-color)!important}:where(input,select,textarea)[aria-invalid=false]:is(:active,:focus):not([type=checkbox],[type=radio]){--pico-box-shadow:0 0 0 var(--pico-outline-width) var(--pico-form-element-valid-focus-color)!important}:where(input,select,textarea)[aria-invalid=true]{--pico-border-color:var(--pico-form-element-invalid-border-color)}:where(input,select,textarea)[aria-invalid=true]:is(:active,:focus){--pico-border-color:var(--pico-form-element-invalid-active-border-color)!important}:where(input,select,textarea)[aria-invalid=true]:is(:active,:focus):not([type=checkbox],[type=radio]){--pico-box-shadow:0 0 0 var(--pico-outline-width) var(--pico-form-element-invalid-focus-color)!important}[dir=rtl] :where(input,select,textarea):not([type=checkbox],[type=radio]):is([aria-invalid],[aria-invalid=true],[aria-invalid=false]){background-position:center left .75rem}input::-webkit-input-placeholder,input::placeholder,select:invalid,textarea::-webkit-input-placeholder,textarea::placeholder{color:var(--pico-form-element-placeholder-color);opacity:1}input:not([type=checkbox],[type=radio]),select,textarea{margin-bottom:var(--pico-spacing)}select::-ms-expand{border:0;background-color:transparent}select:not([multiple],[size]){padding-right:calc(var(--pico-form-element-spacing-horizontal) + 1.5rem);padding-left:var(--pico-form-element-spacing-horizontal);padding-inline-start:var(--pico-form-element-spacing-horizontal);padding-inline-end:calc(var(--pico-form-element-spacing-horizontal) + 1.5rem);background-image:var(--pico-icon-chevron);background-position:center right .75rem;background-size:1rem auto;background-repeat:no-repeat}select[multiple] option:checked{background:var(--pico-form-element-selected-background-color);color:var(--pico-form-element-color)}[dir=rtl] select:not([multiple],[size]){background-position:center left .75rem}textarea{display:block;resize:vertical}textarea[aria-invalid]{--pico-icon-height:calc(1rem * var(--pico-line-height) + var(--pico-form-element-spacing-vertical) * 2 + var(--pico-border-width) * 2);background-position:top right .75rem!important;background-size:1rem var(--pico-icon-height)!important}:where(input,select,textarea,fieldset,.grid)+small{display:block;width:100%;margin-top:calc(var(--pico-spacing) * -.75);margin-bottom:var(--pico-spacing);color:var(--pico-muted-color)}:where(input,select,textarea,fieldset,.grid)[aria-invalid=false]+small{color:var(--pico-ins-color)}:where(input,select,textarea,fieldset,.grid)[aria-invalid=true]+small{color:var(--pico-del-color)}label>:where(input,select,textarea){margin-top:calc(var(--pico-spacing) * .25)}label:has([type=checkbox],[type=radio]){width:-moz-fit-content;width:fit-content;cursor:pointer}[type=checkbox],[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:1.25em;height:1.25em;margin-top:-.125em;margin-inline-end:.5em;border-width:var(--pico-border-width);vertical-align:middle;cursor:pointer}[type=checkbox]::-ms-check,[type=radio]::-ms-check{display:none}[type=checkbox]:checked,[type=checkbox]:checked:active,[type=checkbox]:checked:focus,[type=radio]:checked,[type=radio]:checked:active,[type=radio]:checked:focus{--pico-background-color:var(--pico-primary-background);--pico-border-color:var(--pico-primary-border);background-image:var(--pico-icon-checkbox);background-position:center;background-size:.75em auto;background-repeat:no-repeat}[type=checkbox]~label,[type=radio]~label{display:inline-block;margin-bottom:0;cursor:pointer}[type=checkbox]~label:not(:last-of-type),[type=radio]~label:not(:last-of-type){margin-inline-end:1em}[type=checkbox]:indeterminate{--pico-background-color:var(--pico-primary-background);--pico-border-color:var(--pico-primary-border);background-image:var(--pico-icon-minus);background-position:center;background-size:.75em auto;background-repeat:no-repeat}[type=radio]{border-radius:50%}[type=radio]:checked,[type=radio]:checked:active,[type=radio]:checked:focus{--pico-background-color:var(--pico-primary-inverse);border-width:.35em;background-image:none}[type=checkbox][role=switch]{--pico-background-color:var(--pico-switch-background-color);--pico-color:var(--pico-switch-color);width:2.25em;height:1.25em;border:var(--pico-border-width) solid var(--pico-border-color);border-radius:1.25em;background-color:var(--pico-background-color);line-height:1.25em}[type=checkbox][role=switch]:not([aria-invalid]){--pico-border-color:var(--pico-switch-background-color)}[type=checkbox][role=switch]:before{display:block;aspect-ratio:1;height:100%;border-radius:50%;background-color:var(--pico-color);box-shadow:var(--pico-switch-thumb-box-shadow);content:"";transition:margin .1s ease-in-out}[type=checkbox][role=switch]:focus{--pico-background-color:var(--pico-switch-background-color);--pico-border-color:var(--pico-switch-background-color)}[type=checkbox][role=switch]:checked{--pico-background-color:var(--pico-switch-checked-background-color);--pico-border-color:var(--pico-switch-checked-background-color);background-image:none}[type=checkbox][role=switch]:checked::before{margin-inline-start:calc(2.25em - 1.25em)}[type=checkbox][role=switch][disabled]{--pico-background-color:var(--pico-border-color)}[type=checkbox][aria-invalid=false]:checked,[type=checkbox][aria-invalid=false]:checked:active,[type=checkbox][aria-invalid=false]:checked:focus,[type=checkbox][role=switch][aria-invalid=false]:checked,[type=checkbox][role=switch][aria-invalid=false]:checked:active,[type=checkbox][role=switch][aria-invalid=false]:checked:focus{--pico-background-color:var(--pico-form-element-valid-border-color)}[type=checkbox]:checked:active[aria-invalid=true],[type=checkbox]:checked:focus[aria-invalid=true],[type=checkbox]:checked[aria-invalid=true],[type=checkbox][role=switch]:checked:active[aria-invalid=true],[type=checkbox][role=switch]:checked:focus[aria-invalid=true],[type=checkbox][role=switch]:checked[aria-invalid=true]{--pico-background-color:var(--pico-form-element-invalid-border-color)}[type=checkbox][aria-invalid=false]:checked,[type=checkbox][aria-invalid=false]:checked:active,[type=checkbox][aria-invalid=false]:checked:focus,[type=checkbox][role=switch][aria-invalid=false]:checked,[type=checkbox][role=switch][aria-invalid=false]:checked:active,[type=checkbox][role=switch][aria-invalid=false]:checked:focus,[type=radio][aria-invalid=false]:checked,[type=radio][aria-invalid=false]:checked:active,[type=radio][aria-invalid=false]:checked:focus{--pico-border-color:var(--pico-form-element-valid-border-color)}[type=checkbox]:checked:active[aria-invalid=true],[type=checkbox]:checked:focus[aria-invalid=true],[type=checkbox]:checked[aria-invalid=true],[type=checkbox][role=switch]:checked:active[aria-invalid=true],[type=checkbox][role=switch]:checked:focus[aria-invalid=true],[type=checkbox][role=switch]:checked[aria-invalid=true],[type=radio]:checked:active[aria-invalid=true],[type=radio]:checked:focus[aria-invalid=true],[type=radio]:checked[aria-invalid=true]{--pico-border-color:var(--pico-form-element-invalid-border-color)}[type=color]::-webkit-color-swatch-wrapper{padding:0}[type=color]::-moz-focus-inner{padding:0}[type=color]::-webkit-color-swatch{border:0;border-radius:calc(var(--pico-border-radius) * .5)}[type=color]::-moz-color-swatch{border:0;border-radius:calc(var(--pico-border-radius) * .5)}input:not([type=checkbox],[type=radio],[type=range],[type=file]):is([type=date],[type=datetime-local],[type=month],[type=time],[type=week]){--pico-icon-position:0.75rem;--pico-icon-width:1rem;padding-right:calc(var(--pico-icon-width) + var(--pico-icon-position));background-image:var(--pico-icon-date);background-position:center right var(--pico-icon-position);background-size:var(--pico-icon-width) auto;background-repeat:no-repeat}input:not([type=checkbox],[type=radio],[type=range],[type=file])[type=time]{background-image:var(--pico-icon-time)}[type=date]::-webkit-calendar-picker-indicator,[type=datetime-local]::-webkit-calendar-picker-indicator,[type=month]::-webkit-calendar-picker-indicator,[type=time]::-webkit-calendar-picker-indicator,[type=week]::-webkit-calendar-picker-indicator{width:var(--pico-icon-width);margin-right:calc(var(--pico-icon-width) * -1);margin-left:var(--pico-icon-position);opacity:0}@-moz-document url-prefix(){[type=date],[type=datetime-local],[type=month],[type=time],[type=week]{padding-right:var(--pico-form-element-spacing-horizontal)!important;background-image:none!important}}[dir=rtl] :is([type=date],[type=datetime-local],[type=month],[type=time],[type=week]){text-align:right}[type=file]{--pico-color:var(--pico-muted-color);margin-left:calc(var(--pico-outline-width) * -1);padding:calc(var(--pico-form-element-spacing-vertical) * .5) 0;padding-left:var(--pico-outline-width);border:0;border-radius:0;background:0 0}[type=file]::file-selector-button{margin-right:calc(var(--pico-spacing)/ 2);padding:calc(var(--pico-form-element-spacing-vertical) * .5) var(--pico-form-element-spacing-horizontal)}[type=file]:is(:hover,:active,:focus)::file-selector-button{--pico-background-color:var(--pico-secondary-hover-background);--pico-border-color:var(--pico-secondary-hover-border)}[type=file]:focus::file-selector-button{--pico-box-shadow:var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),0 0 0 var(--pico-outline-width) var(--pico-secondary-focus)}[type=range]{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:100%;height:1.25rem;background:0 0}[type=range]::-webkit-slider-runnable-track{width:100%;height:.375rem;border-radius:var(--pico-border-radius);background-color:var(--pico-range-border-color);-webkit-transition:background-color var(--pico-transition),box-shadow var(--pico-transition);transition:background-color var(--pico-transition),box-shadow var(--pico-transition)}[type=range]::-moz-range-track{width:100%;height:.375rem;border-radius:var(--pico-border-radius);background-color:var(--pico-range-border-color);-moz-transition:background-color var(--pico-transition),box-shadow var(--pico-transition);transition:background-color var(--pico-transition),box-shadow var(--pico-transition)}[type=range]::-ms-track{width:100%;height:.375rem;border-radius:var(--pico-border-radius);background-color:var(--pico-range-border-color);-ms-transition:background-color var(--pico-transition),box-shadow var(--pico-transition);transition:background-color var(--pico-transition),box-shadow var(--pico-transition)}[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:1.25rem;height:1.25rem;margin-top:-.4375rem;border:2px solid var(--pico-range-thumb-border-color);border-radius:50%;background-color:var(--pico-range-thumb-color);cursor:pointer;-webkit-transition:background-color var(--pico-transition),transform var(--pico-transition);transition:background-color var(--pico-transition),transform var(--pico-transition)}[type=range]::-moz-range-thumb{-webkit-appearance:none;width:1.25rem;height:1.25rem;margin-top:-.4375rem;border:2px solid var(--pico-range-thumb-border-color);border-radius:50%;background-color:var(--pico-range-thumb-color);cursor:pointer;-moz-transition:background-color var(--pico-transition),transform var(--pico-transition);transition:background-color var(--pico-transition),transform var(--pico-transition)}[type=range]::-ms-thumb{-webkit-appearance:none;width:1.25rem;height:1.25rem;margin-top:-.4375rem;border:2px solid var(--pico-range-thumb-border-color);border-radius:50%;background-color:var(--pico-range-thumb-color);cursor:pointer;-ms-transition:background-color var(--pico-transition),transform var(--pico-transition);transition:background-color var(--pico-transition),transform var(--pico-transition)}[type=range]:active,[type=range]:focus-within{--pico-range-border-color:var(--pico-range-active-border-color);--pico-range-thumb-color:var(--pico-range-thumb-active-color)}[type=range]:active::-webkit-slider-thumb{transform:scale(1.25)}[type=range]:active::-moz-range-thumb{transform:scale(1.25)}[type=range]:active::-ms-thumb{transform:scale(1.25)}input:not([type=checkbox],[type=radio],[type=range],[type=file])[type=search]{padding-inline-start:calc(var(--pico-form-element-spacing-horizontal) + 1.75rem);background-image:var(--pico-icon-search);background-position:center left calc(var(--pico-form-element-spacing-horizontal) + .125rem);background-size:1rem auto;background-repeat:no-repeat}input:not([type=checkbox],[type=radio],[type=range],[type=file])[type=search][aria-invalid]{padding-inline-start:calc(var(--pico-form-element-spacing-horizontal) + 1.75rem)!important;background-position:center left 1.125rem,center right .75rem}input:not([type=checkbox],[type=radio],[type=range],[type=file])[type=search][aria-invalid=false]{background-image:var(--pico-icon-search),var(--pico-icon-valid)}input:not([type=checkbox],[type=radio],[type=range],[type=file])[type=search][aria-invalid=true]{background-image:var(--pico-icon-search),var(--pico-icon-invalid)}[dir=rtl] :where(input):not([type=checkbox],[type=radio],[type=range],[type=file])[type=search]{background-position:center right 1.125rem}[dir=rtl] :where(input):not([type=checkbox],[type=radio],[type=range],[type=file])[type=search][aria-invalid]{background-position:center right 1.125rem,center left .75rem}details{display:block;margin-bottom:var(--pico-spacing)}details summary{line-height:1rem;list-style-type:none;cursor:pointer;transition:color var(--pico-transition)}details summary:not([role]){color:var(--pico-accordion-close-summary-color)}details summary::-webkit-details-marker{display:none}details summary::marker{display:none}details summary::-moz-list-bullet{list-style-type:none}details summary::after{display:block;width:1rem;height:1rem;margin-inline-start:calc(var(--pico-spacing,1rem) * .5);float:right;transform:rotate(-90deg);background-image:var(--pico-icon-chevron);background-position:right center;background-size:1rem auto;background-repeat:no-repeat;content:"";transition:transform var(--pico-transition)}details summary:focus{outline:0}details summary:focus:not([role]){color:var(--pico-accordion-active-summary-color)}details summary:focus-visible:not([role]){outline:var(--pico-outline-width) solid var(--pico-primary-focus);outline-offset:calc(var(--pico-spacing,1rem) * 0.5);color:var(--pico-primary)}details summary[role=button]{width:100%;text-align:left}details summary[role=button]::after{height:calc(1rem * var(--pico-line-height,1.5))}details[open]>summary{margin-bottom:var(--pico-spacing)}details[open]>summary:not([role]):not(:focus){color:var(--pico-accordion-open-summary-color)}details[open]>summary::after{transform:rotate(0)}[dir=rtl] details summary{text-align:right}[dir=rtl] details summary::after{float:left;background-position:left center}article{margin-bottom:var(--pico-block-spacing-vertical);padding:var(--pico-block-spacing-vertical) var(--pico-block-spacing-horizontal);border-radius:var(--pico-border-radius);background:var(--pico-card-background-color);box-shadow:var(--pico-card-box-shadow)}article>footer,article>header{margin-right:calc(var(--pico-block-spacing-horizontal) * -1);margin-left:calc(var(--pico-block-spacing-horizontal) * -1);padding:calc(var(--pico-block-spacing-vertical) * .66) var(--pico-block-spacing-horizontal);background-color:var(--pico-card-sectioning-background-color)}article>header{margin-top:calc(var(--pico-block-spacing-vertical) * -1);margin-bottom:var(--pico-block-spacing-vertical);border-bottom:var(--pico-border-width) solid var(--pico-card-border-color);border-top-right-radius:var(--pico-border-radius);border-top-left-radius:var(--pico-border-radius)}article>footer{margin-top:var(--pico-block-spacing-vertical);margin-bottom:calc(var(--pico-block-spacing-vertical) * -1);border-top:var(--pico-border-width) solid var(--pico-card-border-color);border-bottom-right-radius:var(--pico-border-radius);border-bottom-left-radius:var(--pico-border-radius)}details.dropdown{position:relative;border-bottom:none}details.dropdown>a::after,details.dropdown>button::after,details.dropdown>summary::after{display:block;width:1rem;height:calc(1rem * var(--pico-line-height,1.5));margin-inline-start:.25rem;float:right;transform:rotate(0) translateX(.2rem);background-image:var(--pico-icon-chevron);background-position:right center;background-size:1rem auto;background-repeat:no-repeat;content:""}nav details.dropdown{margin-bottom:0}details.dropdown>summary:not([role]){height:calc(1rem * var(--pico-line-height) + var(--pico-form-element-spacing-vertical) * 2 + var(--pico-border-width) * 2);padding:var(--pico-form-element-spacing-vertical) var(--pico-form-element-spacing-horizontal);border:var(--pico-border-width) solid var(--pico-form-element-border-color);border-radius:var(--pico-border-radius);background-color:var(--pico-form-element-background-color);color:var(--pico-form-element-placeholder-color);line-height:inherit;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;transition:background-color var(--pico-transition),border-color var(--pico-transition),color var(--pico-transition),box-shadow var(--pico-transition)}details.dropdown>summary:not([role]):active,details.dropdown>summary:not([role]):focus{border-color:var(--pico-form-element-active-border-color);background-color:var(--pico-form-element-active-background-color)}details.dropdown>summary:not([role]):focus{box-shadow:0 0 0 var(--pico-outline-width) var(--pico-form-element-focus-color)}details.dropdown>summary:not([role]):focus-visible{outline:0}details.dropdown>summary:not([role])[aria-invalid=false]{--pico-form-element-border-color:var(--pico-form-element-valid-border-color);--pico-form-element-active-border-color:var(--pico-form-element-valid-focus-color);--pico-form-element-focus-color:var(--pico-form-element-valid-focus-color)}details.dropdown>summary:not([role])[aria-invalid=true]{--pico-form-element-border-color:var(--pico-form-element-invalid-border-color);--pico-form-element-active-border-color:var(--pico-form-element-invalid-focus-color);--pico-form-element-focus-color:var(--pico-form-element-invalid-focus-color)}nav details.dropdown{display:inline;margin:calc(var(--pico-nav-element-spacing-vertical) * -1) 0}nav details.dropdown>summary::after{transform:rotate(0) translateX(0)}nav details.dropdown>summary:not([role]){height:calc(1rem * var(--pico-line-height) + var(--pico-nav-link-spacing-vertical) * 2);padding:calc(var(--pico-nav-link-spacing-vertical) - var(--pico-border-width) * 2) var(--pico-nav-link-spacing-horizontal)}nav details.dropdown>summary:not([role]):focus-visible{box-shadow:0 0 0 var(--pico-outline-width) var(--pico-primary-focus)}details.dropdown>summary+ul{display:flex;z-index:99;position:absolute;left:0;flex-direction:column;width:100%;min-width:-moz-fit-content;min-width:fit-content;margin:0;margin-top:var(--pico-outline-width);padding:0;border:var(--pico-border-width) solid var(--pico-dropdown-border-color);border-radius:var(--pico-border-radius);background-color:var(--pico-dropdown-background-color);box-shadow:var(--pico-dropdown-box-shadow);color:var(--pico-dropdown-color);white-space:nowrap;opacity:0;transition:opacity var(--pico-transition),transform 0s ease-in-out 1s}details.dropdown>summary+ul[dir=rtl]{right:0;left:auto}details.dropdown>summary+ul li{width:100%;margin-bottom:0;padding:calc(var(--pico-form-element-spacing-vertical) * .5) var(--pico-form-element-spacing-horizontal);list-style:none}details.dropdown>summary+ul li:first-of-type{margin-top:calc(var(--pico-form-element-spacing-vertical) * .5)}details.dropdown>summary+ul li:last-of-type{margin-bottom:calc(var(--pico-form-element-spacing-vertical) * .5)}details.dropdown>summary+ul li a{display:block;margin:calc(var(--pico-form-element-spacing-vertical) * -.5) calc(var(--pico-form-element-spacing-horizontal) * -1);padding:calc(var(--pico-form-element-spacing-vertical) * .5) var(--pico-form-element-spacing-horizontal);overflow:hidden;border-radius:0;color:var(--pico-dropdown-color);text-decoration:none;text-overflow:ellipsis}details.dropdown>summary+ul li a:active,details.dropdown>summary+ul li a:focus,details.dropdown>summary+ul li a:focus-visible,details.dropdown>summary+ul li a:hover,details.dropdown>summary+ul li a[aria-current]:not([aria-current=false]){background-color:var(--pico-dropdown-hover-background-color)}details.dropdown>summary+ul li label{width:100%}details.dropdown>summary+ul li:has(label):hover{background-color:var(--pico-dropdown-hover-background-color)}details.dropdown[open]>summary{margin-bottom:0}details.dropdown[open]>summary+ul{transform:scaleY(1);opacity:1;transition:opacity var(--pico-transition),transform 0s ease-in-out 0s}details.dropdown[open]>summary::before{display:block;z-index:1;position:fixed;width:100vw;height:100vh;inset:0;background:0 0;content:"";cursor:default}label>details.dropdown{margin-top:calc(var(--pico-spacing) * .25)}[role=group],[role=search]{display:inline-flex;position:relative;width:100%;margin-bottom:var(--pico-spacing);border-radius:var(--pico-border-radius);box-shadow:var(--pico-group-box-shadow,0 0 0 transparent);vertical-align:middle;transition:box-shadow var(--pico-transition)}[role=group] input:not([type=checkbox],[type=radio]),[role=group] select,[role=group]>*,[role=search] input:not([type=checkbox],[type=radio]),[role=search] select,[role=search]>*{position:relative;flex:1 1 auto;margin-bottom:0}[role=group] input:not([type=checkbox],[type=radio]):not(:first-child),[role=group] select:not(:first-child),[role=group]>:not(:first-child),[role=search] input:not([type=checkbox],[type=radio]):not(:first-child),[role=search] select:not(:first-child),[role=search]>:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}[role=group] input:not([type=checkbox],[type=radio]):not(:last-child),[role=group] select:not(:last-child),[role=group]>:not(:last-child),[role=search] input:not([type=checkbox],[type=radio]):not(:last-child),[role=search] select:not(:last-child),[role=search]>:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}[role=group] input:not([type=checkbox],[type=radio]):focus,[role=group] select:focus,[role=group]>:focus,[role=search] input:not([type=checkbox],[type=radio]):focus,[role=search] select:focus,[role=search]>:focus{z-index:2}[role=group] [role=button]:not(:first-child),[role=group] [type=button]:not(:first-child),[role=group] [type=reset]:not(:first-child),[role=group] [type=submit]:not(:first-child),[role=group] button:not(:first-child),[role=group] input:not([type=checkbox],[type=radio]):not(:first-child),[role=group] select:not(:first-child),[role=search] [role=button]:not(:first-child),[role=search] [type=button]:not(:first-child),[role=search] [type=reset]:not(:first-child),[role=search] [type=submit]:not(:first-child),[role=search] button:not(:first-child),[role=search] input:not([type=checkbox],[type=radio]):not(:first-child),[role=search] select:not(:first-child){margin-left:calc(var(--pico-border-width) * -1)}[role=group] [role=button],[role=group] [type=button],[role=group] [type=reset],[role=group] [type=submit],[role=group] button,[role=search] [role=button],[role=search] [type=button],[role=search] [type=reset],[role=search] [type=submit],[role=search] button{width:auto}@supports selector(:has(*)){[role=group]:has(button:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus),[role=search]:has(button:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus){--pico-group-box-shadow:var(--pico-group-box-shadow-focus-with-button)}[role=group]:has(button:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus) input:not([type=checkbox],[type=radio]),[role=group]:has(button:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus) select,[role=search]:has(button:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus) input:not([type=checkbox],[type=radio]),[role=search]:has(button:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus) select{border-color:transparent}[role=group]:has(input:not([type=submit],[type=button]):focus,select:focus),[role=search]:has(input:not([type=submit],[type=button]):focus,select:focus){--pico-group-box-shadow:var(--pico-group-box-shadow-focus-with-input)}[role=group]:has(input:not([type=submit],[type=button]):focus,select:focus) [role=button],[role=group]:has(input:not([type=submit],[type=button]):focus,select:focus) [type=button],[role=group]:has(input:not([type=submit],[type=button]):focus,select:focus) [type=submit],[role=group]:has(input:not([type=submit],[type=button]):focus,select:focus) button,[role=search]:has(input:not([type=submit],[type=button]):focus,select:focus) [role=button],[role=search]:has(input:not([type=submit],[type=button]):focus,select:focus) [type=button],[role=search]:has(input:not([type=submit],[type=button]):focus,select:focus) [type=submit],[role=search]:has(input:not([type=submit],[type=button]):focus,select:focus) button{--pico-button-box-shadow:0 0 0 var(--pico-border-width) var(--pico-primary-border);--pico-button-hover-box-shadow:0 0 0 var(--pico-border-width) var(--pico-primary-hover-border)}[role=group] [role=button]:focus,[role=group] [type=button]:focus,[role=group] [type=reset]:focus,[role=group] [type=submit]:focus,[role=group] button:focus,[role=search] [role=button]:focus,[role=search] [type=button]:focus,[role=search] [type=reset]:focus,[role=search] [type=submit]:focus,[role=search] button:focus{box-shadow:none}}[role=search]>:first-child{border-top-left-radius:5rem;border-bottom-left-radius:5rem}[role=search]>:last-child{border-top-right-radius:5rem;border-bottom-right-radius:5rem}[aria-busy=true]:not(input,select,textarea,html,form){white-space:nowrap}[aria-busy=true]:not(input,select,textarea,html,form)::before{display:inline-block;width:1em;height:1em;background-image:var(--pico-icon-loading);background-size:1em auto;background-repeat:no-repeat;content:"";vertical-align:-.125em}[aria-busy=true]:not(input,select,textarea,html,form):not(:empty)::before{margin-inline-end:calc(var(--pico-spacing) * .5)}[aria-busy=true]:not(input,select,textarea,html,form):empty{text-align:center}[role=button][aria-busy=true],[type=button][aria-busy=true],[type=reset][aria-busy=true],[type=submit][aria-busy=true],a[aria-busy=true],button[aria-busy=true]{pointer-events:none}:host,:root{--pico-scrollbar-width:0px}dialog{display:flex;z-index:999;position:fixed;top:0;right:0;bottom:0;left:0;align-items:center;justify-content:center;width:inherit;min-width:100%;height:inherit;min-height:100%;padding:0;border:0;-webkit-backdrop-filter:var(--pico-modal-overlay-backdrop-filter);backdrop-filter:var(--pico-modal-overlay-backdrop-filter);background-color:var(--pico-modal-overlay-background-color);color:var(--pico-color)}dialog>article{width:100%;max-height:calc(100vh - var(--pico-spacing) * 2);margin:var(--pico-spacing);overflow:auto}@media (min-width:576px){dialog>article{max-width:510px}}@media (min-width:768px){dialog>article{max-width:700px}}dialog>article>header>*{margin-bottom:0}dialog>article>header .close,dialog>article>header :is(a,button)[rel=prev]{margin:0;margin-left:var(--pico-spacing);padding:0;float:right}dialog>article>footer{text-align:right}dialog>article>footer [role=button],dialog>article>footer button{margin-bottom:0}dialog>article>footer [role=button]:not(:first-of-type),dialog>article>footer button:not(:first-of-type){margin-left:calc(var(--pico-spacing) * .5)}dialog>article .close,dialog>article :is(a,button)[rel=prev]{display:block;width:1rem;height:1rem;margin-top:calc(var(--pico-spacing) * -1);margin-bottom:var(--pico-spacing);margin-left:auto;border:none;background-image:var(--pico-icon-close);background-position:center;background-size:auto 1rem;background-repeat:no-repeat;background-color:transparent;opacity:.5;transition:opacity var(--pico-transition)}dialog>article .close:is([aria-current]:not([aria-current=false]),:hover,:active,:focus),dialog>article :is(a,button)[rel=prev]:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){opacity:1}dialog:not([open]),dialog[open=false]{display:none}.modal-is-open{padding-right:var(--pico-scrollbar-width,0);overflow:hidden;pointer-events:none;touch-action:none}.modal-is-open dialog{pointer-events:auto;touch-action:auto}:where(.modal-is-opening,.modal-is-closing) dialog,:where(.modal-is-opening,.modal-is-closing) dialog>article{animation-duration:.2s;animation-timing-function:ease-in-out;animation-fill-mode:both}:where(.modal-is-opening,.modal-is-closing) dialog{animation-duration:.8s;animation-name:modal-overlay}:where(.modal-is-opening,.modal-is-closing) dialog>article{animation-delay:.2s;animation-name:modal}.modal-is-closing dialog,.modal-is-closing dialog>article{animation-delay:0s;animation-direction:reverse}@keyframes modal-overlay{from{-webkit-backdrop-filter:none;backdrop-filter:none;background-color:transparent}}@keyframes modal{from{transform:translateY(-100%);opacity:0}}:where(nav li)::before{float:left;content:"​"}nav,nav ul{display:flex}nav{justify-content:space-between;overflow:visible}nav ol,nav ul{align-items:center;margin-bottom:0;padding:0;list-style:none}nav ol:first-of-type,nav ul:first-of-type{margin-left:calc(var(--pico-nav-element-spacing-horizontal) * -1)}nav ol:last-of-type,nav ul:last-of-type{margin-right:calc(var(--pico-nav-element-spacing-horizontal) * -1)}nav li{display:inline-block;margin:0;padding:var(--pico-nav-element-spacing-vertical) var(--pico-nav-element-spacing-horizontal)}nav li :where(a,[role=link]){display:inline-block;margin:calc(var(--pico-nav-link-spacing-vertical) * -1) calc(var(--pico-nav-link-spacing-horizontal) * -1);padding:var(--pico-nav-link-spacing-vertical) var(--pico-nav-link-spacing-horizontal);border-radius:var(--pico-border-radius)}nav li :where(a,[role=link]):not(:hover){text-decoration:none}nav li [role=button],nav li [type=button],nav li button,nav li input:not([type=checkbox],[type=radio],[type=range],[type=file]),nav li select{height:auto;margin-right:inherit;margin-bottom:0;margin-left:inherit;padding:calc(var(--pico-nav-link-spacing-vertical) - var(--pico-border-width) * 2) var(--pico-nav-link-spacing-horizontal)}nav[aria-label=breadcrumb]{align-items:center;justify-content:start}nav[aria-label=breadcrumb] ul li:not(:first-child){margin-inline-start:var(--pico-nav-link-spacing-horizontal)}nav[aria-label=breadcrumb] ul li a{margin:calc(var(--pico-nav-link-spacing-vertical) * -1) 0;margin-inline-start:calc(var(--pico-nav-link-spacing-horizontal) * -1)}nav[aria-label=breadcrumb] ul li:not(:last-child)::after{display:inline-block;position:absolute;width:calc(var(--pico-nav-link-spacing-horizontal) * 4);margin:0 calc(var(--pico-nav-link-spacing-horizontal) * -1);content:var(--pico-nav-breadcrumb-divider);color:var(--pico-muted-color);text-align:center;text-decoration:none;white-space:nowrap}nav[aria-label=breadcrumb] a[aria-current]:not([aria-current=false]){background-color:transparent;color:inherit;text-decoration:none;pointer-events:none}aside li,aside nav,aside ol,aside ul{display:block}aside li{padding:calc(var(--pico-nav-element-spacing-vertical) * .5) var(--pico-nav-element-spacing-horizontal)}aside li a{display:block}aside li [role=button]{margin:inherit}[dir=rtl] nav[aria-label=breadcrumb] ul li:not(:last-child) ::after{content:"\\"}progress{display:inline-block;vertical-align:baseline}progress{-webkit-appearance:none;-moz-appearance:none;display:inline-block;appearance:none;width:100%;height:.5rem;margin-bottom:calc(var(--pico-spacing) * .5);overflow:hidden;border:0;border-radius:var(--pico-border-radius);background-color:var(--pico-progress-background-color);color:var(--pico-progress-color)}progress::-webkit-progress-bar{border-radius:var(--pico-border-radius);background:0 0}progress[value]::-webkit-progress-value{background-color:var(--pico-progress-color);-webkit-transition:inline-size var(--pico-transition);transition:inline-size var(--pico-transition)}progress::-moz-progress-bar{background-color:var(--pico-progress-color)}@media (prefers-reduced-motion:no-preference){progress:indeterminate{background:var(--pico-progress-background-color) linear-gradient(to right,var(--pico-progress-color) 30%,var(--pico-progress-background-color) 30%) top left/150% 150% no-repeat;animation:progress-indeterminate 1s linear infinite}progress:indeterminate[value]::-webkit-progress-value{background-color:transparent}progress:indeterminate::-moz-progress-bar{background-color:transparent}}@media (prefers-reduced-motion:no-preference){[dir=rtl] progress:indeterminate{animation-direction:reverse}}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}[data-tooltip]{position:relative}[data-tooltip]:not(a,button,input,[role=button]){border-bottom:1px dotted;text-decoration:none;cursor:help}[data-tooltip]::after,[data-tooltip]::before,[data-tooltip][data-placement=top]::after,[data-tooltip][data-placement=top]::before{display:block;z-index:99;position:absolute;bottom:100%;left:50%;padding:.25rem .5rem;overflow:hidden;transform:translate(-50%,-.25rem);border-radius:var(--pico-border-radius);background:var(--pico-tooltip-background-color);content:attr(data-tooltip);color:var(--pico-tooltip-color);font-style:normal;font-weight:var(--pico-font-weight);font-size:.875rem;text-decoration:none;text-overflow:ellipsis;white-space:nowrap;opacity:0;pointer-events:none}[data-tooltip]::after,[data-tooltip][data-placement=top]::after{padding:0;transform:translate(-50%,0);border-top:.3rem solid;border-right:.3rem solid transparent;border-left:.3rem solid transparent;border-radius:0;background-color:transparent;content:"";color:var(--pico-tooltip-background-color)}[data-tooltip][data-placement=bottom]::after,[data-tooltip][data-placement=bottom]::before{top:100%;bottom:auto;transform:translate(-50%,.25rem)}[data-tooltip][data-placement=bottom]:after{transform:translate(-50%,-.3rem);border:.3rem solid transparent;border-bottom:.3rem solid}[data-tooltip][data-placement=left]::after,[data-tooltip][data-placement=left]::before{top:50%;right:100%;bottom:auto;left:auto;transform:translate(-.25rem,-50%)}[data-tooltip][data-placement=left]:after{transform:translate(.3rem,-50%);border:.3rem solid transparent;border-left:.3rem solid}[data-tooltip][data-placement=right]::after,[data-tooltip][data-placement=right]::before{top:50%;right:auto;bottom:auto;left:100%;transform:translate(.25rem,-50%)}[data-tooltip][data-placement=right]:after{transform:translate(-.3rem,-50%);border:.3rem solid transparent;border-right:.3rem solid}[data-tooltip]:focus::after,[data-tooltip]:focus::before,[data-tooltip]:hover::after,[data-tooltip]:hover::before{opacity:1}@media (hover:hover) and (pointer:fine){[data-tooltip]:focus::after,[data-tooltip]:focus::before,[data-tooltip]:hover::after,[data-tooltip]:hover::before{--pico-tooltip-slide-to:translate(-50%, -0.25rem);transform:translate(-50%,.75rem);animation-duration:.2s;animation-fill-mode:forwards;animation-name:tooltip-slide;opacity:0}[data-tooltip]:focus::after,[data-tooltip]:hover::after{--pico-tooltip-caret-slide-to:translate(-50%, 0rem);transform:translate(-50%,-.25rem);animation-name:tooltip-caret-slide}[data-tooltip][data-placement=bottom]:focus::after,[data-tooltip][data-placement=bottom]:focus::before,[data-tooltip][data-placement=bottom]:hover::after,[data-tooltip][data-placement=bottom]:hover::before{--pico-tooltip-slide-to:translate(-50%, 0.25rem);transform:translate(-50%,-.75rem);animation-name:tooltip-slide}[data-tooltip][data-placement=bottom]:focus::after,[data-tooltip][data-placement=bottom]:hover::after{--pico-tooltip-caret-slide-to:translate(-50%, -0.3rem);transform:translate(-50%,-.5rem);animation-name:tooltip-caret-slide}[data-tooltip][data-placement=left]:focus::after,[data-tooltip][data-placement=left]:focus::before,[data-tooltip][data-placement=left]:hover::after,[data-tooltip][data-placement=left]:hover::before{--pico-tooltip-slide-to:translate(-0.25rem, -50%);transform:translate(.75rem,-50%);animation-name:tooltip-slide}[data-tooltip][data-placement=left]:focus::after,[data-tooltip][data-placement=left]:hover::after{--pico-tooltip-caret-slide-to:translate(0.3rem, -50%);transform:translate(.05rem,-50%);animation-name:tooltip-caret-slide}[data-tooltip][data-placement=right]:focus::after,[data-tooltip][data-placement=right]:focus::before,[data-tooltip][data-placement=right]:hover::after,[data-tooltip][data-placement=right]:hover::before{--pico-tooltip-slide-to:translate(0.25rem, -50%);transform:translate(-.75rem,-50%);animation-name:tooltip-slide}[data-tooltip][data-placement=right]:focus::after,[data-tooltip][data-placement=right]:hover::after{--pico-tooltip-caret-slide-to:translate(-0.3rem, -50%);transform:translate(-.05rem,-50%);animation-name:tooltip-caret-slide}}@keyframes tooltip-slide{to{transform:var(--pico-tooltip-slide-to);opacity:1}}@keyframes tooltip-caret-slide{50%{opacity:0}to{transform:var(--pico-tooltip-caret-slide-to);opacity:1}}[aria-controls]{cursor:pointer}[aria-disabled=true],[disabled]{cursor:not-allowed}[aria-hidden=false][hidden]{display:initial}[aria-hidden=false][hidden]:not(:focus){clip:rect(0,0,0,0);position:absolute}[tabindex],a,area,button,input,label,select,summary,textarea{-ms-touch-action:manipulation}[dir=rtl]{direction:rtl}@media (prefers-reduced-motion:reduce){:not([aria-busy=true]),:not([aria-busy=true])::after,:not([aria-busy=true])::before{background-attachment:initial!important;animation-duration:1ms!important;animation-delay:-1ms!important;animation-iteration-count:1!important;scroll-behavior:auto!important;transition-delay:0s!important;transition-duration:0s!important}} \ No newline at end of file diff --git a/web_test/PLC S7-315 Streamer & Logger_files/plc.js.descargar b/web_test/PLC S7-315 Streamer & Logger_files/plc.js.descargar deleted file mode 100644 index 9f3fd1d..0000000 --- a/web_test/PLC S7-315 Streamer & Logger_files/plc.js.descargar +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Gestión de la conexión con el PLC y configuración relacionada - */ - -// Inicializar listeners de eventos para PLC -function initPlcListeners() { - // Configuración del PLC - document.getElementById('plc-config-form').addEventListener('submit', function (e) { - e.preventDefault(); - const data = { - ip: document.getElementById('plc-ip').value, - rack: parseInt(document.getElementById('plc-rack').value), - slot: parseInt(document.getElementById('plc-slot').value) - }; - - fetch('/api/plc/config', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(data) - }) - .then(response => response.json()) - .then(data => { - showMessage(data.message, data.success ? 'success' : 'error'); - }); - }); - - // Configuración UDP - document.getElementById('udp-config-form').addEventListener('submit', function (e) { - e.preventDefault(); - const data = { - host: document.getElementById('udp-host').value, - port: parseInt(document.getElementById('udp-port').value) - }; - - fetch('/api/udp/config', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(data) - }) - .then(response => response.json()) - .then(data => { - showMessage(data.message, data.success ? 'success' : 'error'); - }); - }); - - // Botón de conexión PLC - document.getElementById('connect-btn').addEventListener('click', function () { - fetch('/api/plc/connect', { method: 'POST' }) - .then(response => response.json()) - .then(data => { - showMessage(data.message, data.success ? 'success' : 'error'); - updateStatus(); - }); - }); - - // Botón de desconexión PLC - document.getElementById('disconnect-btn').addEventListener('click', function () { - fetch('/api/plc/disconnect', { method: 'POST' }) - .then(response => response.json()) - .then(data => { - showMessage(data.message, data.success ? 'success' : 'error'); - updateStatus(); - }); - }); - - // Botón de actualización de intervalo - document.getElementById('update-sampling-btn').addEventListener('click', function () { - const interval = parseFloat(document.getElementById('sampling-interval').value); - fetch('/api/sampling', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ interval: interval }) - }) - .then(response => response.json()) - .then(data => { - showMessage(data.message, data.success ? 'success' : 'error'); - }); - }); -} \ No newline at end of file diff --git a/web_test/PLC S7-315 Streamer & Logger_files/plotting.js.descargar b/web_test/PLC S7-315 Streamer & Logger_files/plotting.js.descargar deleted file mode 100644 index 6eb91b2..0000000 --- a/web_test/PLC S7-315 Streamer & Logger_files/plotting.js.descargar +++ /dev/null @@ -1,1461 +0,0 @@ -/** - * 📈 Real-Time Plotting System - * Maneja sesiones de plotting con Chart.js y comunicación con el backend - */ - -// 🔧 Global debugging para plots -window.enablePlotDebug = () => { - window.plotDebugEnabled = true; - console.log('📈 Plot debugging enabled. Check console for detailed logs.'); -}; - -window.disablePlotDebug = () => { - window.plotDebugEnabled = false; - console.log('📈 Plot debugging disabled.'); -}; - -// Helper function para logging condicional -function plotDebugLog(...args) { - if (window.plotDebugEnabled) { - console.debug(...args); - } -} - -// 🔧 Test function para verificar que todo funciona -window.testPlotSystem = async () => { - console.log('📈 Testing plot system...'); - - try { - // Test 1: Verificar disponibilidad de variables - const varsResponse = await fetch('/api/plots/variables'); - const varsData = await varsResponse.json(); - console.log('✅ Available variables:', varsData.available_variables.length); - console.log('✅ Boolean variables for triggers:', varsData.boolean_variables.length); - - // Test 2: Verificar plots existentes - const plotsResponse = await fetch('/api/plots'); - const plotsData = await plotsResponse.json(); - console.log('✅ Existing plot sessions:', plotsData.sessions.length); - - // Test 3: Si hay plots, verificar datos - if (plotsData.sessions.length > 0) { - const sessionId = plotsData.sessions[0].session_id; - const dataResponse = await fetch(`/api/plots/${sessionId}/data`); - const dataResult = await dataResponse.json(); - console.log(`✅ Plot ${sessionId} has ${dataResult.datasets?.length || 0} datasets with ${dataResult.data_points_count || 0} points`); - } - - console.log('📈 Plot system test completed. Enable debug with enablePlotDebug() for detailed logs.'); - - } catch (error) { - console.error('❌ Plot system test failed:', error); - } -}; - -class PlotManager { - constructor() { - this.sessions = new Map(); // session_id -> Chart instance - this.updateInterval = null; - this.statusUpdateInterval = null; // 🔑 NUEVO: Para updates de status - this.isInitialized = false; - - // Colores para las variables - this.colors = [ - '#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF', - '#FF9F40', '#8A2BE2', '#FF1493', '#00CED1', '#32CD32', - '#FFB347', '#DA70D6', '#40E0D0', '#EE82EE', '#90EE90' - ]; - - // Variables seleccionadas para el formulario - this.selectedVariables = new Map(); // variable_name -> { color, dataset } - this.availableDatasets = []; - this.currentEditingSession = null; - - this.init(); - } - - init() { - // Cargar sesiones existentes al inicializar - this.loadExistingSessions(); - - // Inicializar formulario colapsable - this.initCollapsibleForm(); - - // Inicializar modal de selección de variables - this.initVariableModal(); - - // Iniciar actualización automática - this.startAutoUpdate(); - - this.isInitialized = true; - console.log('📈 Plot Manager initialized'); - } - - async loadExistingSessions() { - try { - const response = await fetch('/api/plots'); - const data = await response.json(); - - if (data.sessions) { - for (const session of data.sessions) { - // 🔑 NUEVO: Obtener configuración completa para cada sesión - try { - const configResponse = await fetch(`/api/plots/${session.session_id}/config`); - const configData = await configResponse.json(); - - let sessionInfo = session; - - // Si tenemos la configuración completa, usar esa información - if (configData.success && configData.config) { - sessionInfo = { - ...session, - is_active: configData.config.is_active, - is_paused: configData.config.is_paused, - variables_count: configData.config.variables ? configData.config.variables.length : session.variables_count, - trigger_enabled: configData.config.trigger_enabled, - trigger_variable: configData.config.trigger_variable, - trigger_on_true: configData.config.trigger_on_true - }; - } - - await this.createPlotSessionTab(session.session_id, sessionInfo); - } catch (configError) { - console.error(`Error loading config for session ${session.session_id}:`, configError); - // Usar información básica si falla la carga de configuración - await this.createPlotSessionTab(session.session_id, session); - } - } - } - } catch (error) { - console.error('Error loading existing plot sessions:', error); - } - } - - startAutoUpdate() { - // Actualizar datos cada 500ms para plots activos - this.updateInterval = setInterval(() => { - this.updateAllSessions(); - }, 500); - - // 🔑 NUEVO: Actualizar status cada 2 segundos para mantener sincronización - this.statusUpdateInterval = setInterval(() => { - this.updateAllSessionsStatus(); - }, 2000); - } - - stopAutoUpdate() { - if (this.updateInterval) { - clearInterval(this.updateInterval); - this.updateInterval = null; - } - - // 🔑 NUEVO: Detener también el update de status - if (this.statusUpdateInterval) { - clearInterval(this.statusUpdateInterval); - this.statusUpdateInterval = null; - } - } - - async updateAllSessions() { - const activeSessions = Array.from(this.sessions.keys()); - - for (const sessionId of activeSessions) { - await this.updateSessionData(sessionId); - } - } - - // 🔑 NUEVO: Actualizar status de todas las sesiones - async updateAllSessionsStatus() { - const activeSessions = Array.from(this.sessions.keys()); - - for (const sessionId of activeSessions) { - await this.updateSessionStatus(sessionId); - } - } - - async updateSessionData(sessionId) { - try { - // 🚀 NUEVO: Para streaming, el refreshStreamingData maneja la actualización automática - // Esta función ahora es principalmente para compatibilidad y casos especiales - const sessionData = this.sessions.get(sessionId); - if (!sessionData || !sessionData.chart) { - return; - } - - const response = await fetch(`/api/plots/${sessionId}/data`); - const plotData = await response.json(); - - if (plotData.datasets) { - // 🔧 DEBUG: Log para troubleshooting - if (plotData.datasets.length > 1) { - plotDebugLog(`📈 Plot ${sessionId}: Manual update ${plotData.datasets.length} variables, ${plotData.data_points_count} total points`); - } - this.updateChart(sessionId, plotData); - } else { - console.warn(`📈 Plot ${sessionId}: No datasets received from API`); - } - } catch (error) { - console.error(`Error updating session ${sessionId}:`, error); - } - } - - createPlotSession(sessionId, config) { - // Crear contenedor para el plot - const container = document.createElement('div'); - container.className = 'plot-session'; - container.id = `plot-${sessionId}`; - - container.innerHTML = ` -
-

📈 ${config.name || `Plot ${sessionId}`}

-
- - - - - -
-
-
- - Variables: ${config.variables_count || 0} | - Data Points: 0 | - ${config.trigger_enabled ? `Trigger: ${config.trigger_variable} (${config.trigger_on_true ? 'True' : 'False'})` : 'No Trigger'} - -
-
- -
- `; - - document.getElementById('plot-sessions-container').appendChild(container); - - // 🚀 NUEVO: Usar chartjs-plugin-streaming para crear chart - const ctx = document.getElementById(`chart-${sessionId}`).getContext('2d'); - - // Configurar streaming con la nueva librería - const streamingConfig = window.ChartStreaming.createStreamingChartConfig({ - duration: (config.time_window || 60) * 1000, // Convertir a milisegundos - refresh: 500, // Actualizar cada 500ms - frameRate: 30, - pause: false, - yMin: config.y_min, - yMax: config.y_max, - onRefresh: (chart) => { - // Esta función se llama automáticamente para obtener nuevos datos - this.refreshStreamingData(sessionId, chart); - } - }); - - const chart = new Chart(ctx, streamingConfig); - - this.sessions.set(sessionId, { - chart: chart, - config: config, - lastDataFetch: 0, - datasets: new Map() // Para tracking de datasets por variable - }); - - // Inicializar datasets para las variables del plot - this.initializeStreamingDatasets(sessionId, config); - - console.log(`📈 Created streaming plot session: ${sessionId}`); - } - - updateChart(sessionId, plotData) { - const sessionData = this.sessions.get(sessionId); - if (!sessionData || !sessionData.chart) { - console.warn(`📈 Plot ${sessionId}: Chart not found in sessions`); - return; - } - - const chart = sessionData.chart; - - // 🔧 DEBUG: Verificar datos antes de actualizar - if (plotData.datasets && plotData.datasets.length > 0) { - plotDebugLog(`📈 Plot ${sessionId}: Updating streaming chart with ${plotData.datasets.length} datasets`); - plotData.datasets.forEach((dataset, idx) => { - plotDebugLog(` - Variable ${idx + 1}: ${dataset.label} (${dataset.data.length} points)`); - }); - } - - // 🚀 NUEVO: Para streaming, agregamos nuevos datos en lugar de reemplazar todo - this.updateStreamingData(sessionId, plotData); - - // Actualizar escalas Y - mejore el auto-scaling - if (plotData.y_min !== undefined && plotData.y_max !== undefined) { - chart.options.scales.y.min = plotData.y_min; - chart.options.scales.y.max = plotData.y_max; - } else { - // Auto-scaling mejorado cuando no hay límites definidos - chart.options.scales.y.min = undefined; - chart.options.scales.y.max = undefined; - } - - // Actualizar contador de puntos - const pointsElement = document.getElementById(`points-${sessionId}`); - if (pointsElement) { - const totalPoints = plotData.data_points_count || 0; - pointsElement.textContent = totalPoints; - - // 🔧 DEBUG: Log si el contador no coincide - const calculatedPoints = (plotData.datasets || []).reduce((sum, dataset) => sum + (dataset.data?.length || 0), 0); - if (totalPoints !== calculatedPoints) { - plotDebugLog(`📈 Plot ${sessionId}: Points mismatch - reported: ${totalPoints}, calculated: ${calculatedPoints}`); - } - } - - // 🔑 NUEVO: Actualizar status visual del plot - if (plotData.is_active !== undefined || plotData.is_paused !== undefined) { - this.updatePlotStats(sessionId, { - variables_count: plotData.datasets ? plotData.datasets.length : 0, - is_active: plotData.is_active, - is_paused: plotData.is_paused, - trigger_enabled: plotData.trigger_enabled, - trigger_variable: plotData.trigger_variable, - trigger_on_true: plotData.trigger_on_true - }); - } - - // El chart de streaming se actualiza automáticamente, no necesitamos llamar update manualmente - } - - async controlPlot(sessionId, action) { - try { - // 🚀 NUEVO: Controlar streaming localmente para algunas acciones - if (action === 'pause') { - this.setStreamingPause(sessionId, true); - } else if (action === 'start') { - this.setStreamingPause(sessionId, false); - } else if (action === 'clear') { - this.clearStreamingData(sessionId); - } - - const response = await fetch(`/api/plots/${sessionId}/control`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ action: action }) - }); - - const result = await response.json(); - - if (result.success) { - // 🔑 NUEVO: Actualizar status inmediatamente después de la acción - await this.updateSessionStatus(sessionId); - - // Para stop, también pausar el streaming - if (action === 'stop') { - this.setStreamingPause(sessionId, true); - } - - // Para start, asegurar que el streaming esté activo - if (action === 'start') { - this.setStreamingPause(sessionId, false); - } - - showNotification(result.message, 'success'); - } else { - showNotification(result.error, 'error'); - } - } catch (error) { - console.error(`Error controlling plot ${sessionId}:`, error); - showNotification('Error controlling plot session', 'error'); - } - } - - // 🔑 NUEVO: Método para actualizar solo el status del plot - async updateSessionStatus(sessionId) { - try { - const response = await fetch(`/api/plots/${sessionId}/config`); - const data = await response.json(); - - if (data.success && data.config) { - this.updatePlotStats(sessionId, data.config); - - // 🚀 NUEVO: Actualizar estado de streaming basado en el status del backend - const sessionData = this.sessions.get(sessionId); - if (sessionData) { - // Actualizar configuración local - sessionData.config = data.config; - - // Controlar pausa del streaming basado en el estado del plot - const shouldPause = !data.config.is_active || data.config.is_paused; - this.setStreamingPause(sessionId, shouldPause); - } - } - } catch (error) { - console.error(`Error updating session status ${sessionId}:`, error); - } - } - - async removePlot(sessionId) { - try { - const response = await fetch(`/api/plots/${sessionId}`, { - method: 'DELETE' - }); - - const result = await response.json(); - - if (result.success) { - this.removeChart(sessionId); - showNotification('Plot session removed', 'success'); - } else { - showNotification(result.error, 'error'); - } - } catch (error) { - console.error(`Error removing plot ${sessionId}:`, error); - showNotification('Error removing plot session', 'error'); - } - } - - removeChart(sessionId) { - // Destruir Chart.js - const sessionData = this.sessions.get(sessionId); - if (sessionData && sessionData.chart) { - sessionData.chart.destroy(); - this.sessions.delete(sessionId); - } - - // Remover contenedor - const container = document.getElementById(`plot-${sessionId}`); - if (container) { - container.remove(); - } - } - - // 🚀 NUEVAS FUNCIONES PARA STREAMING - - /** - * Inicializa los datasets de streaming para las variables del plot - */ - initializeStreamingDatasets(sessionId, config) { - const sessionData = this.sessions.get(sessionId); - if (!sessionData || !sessionData.chart || !config.variables) { - return; - } - - const chart = sessionData.chart; - const datasets = []; - - config.variables.forEach((variable, index) => { - const color = this.getColor(variable, index); - const dataset = { - label: variable, - data: [], - borderColor: color, - backgroundColor: color + '20', // Color con transparencia - borderWidth: 2, - fill: false, - pointRadius: 0, - pointHoverRadius: 3, - tension: 0.1 - }; - - datasets.push(dataset); - sessionData.datasets.set(variable, index); - }); - - chart.data.datasets = datasets; - chart.update('quiet'); - - plotDebugLog(`📈 Plot ${sessionId}: Initialized ${datasets.length} streaming datasets`); - } - - /** - * Función llamada automáticamente por chartjs-plugin-streaming para obtener nuevos datos - */ - async refreshStreamingData(sessionId, chart) { - try { - // Evitar llamadas muy frecuentes - const sessionData = this.sessions.get(sessionId); - if (!sessionData) return; - - const now = Date.now(); - if (now - sessionData.lastDataFetch < 400) { // Mínimo 400ms entre llamadas - return; - } - sessionData.lastDataFetch = now; - - // Obtener datos del backend - const response = await fetch(`/api/plots/${sessionId}/data`); - const plotData = await response.json(); - - if (plotData && plotData.datasets) { - // Para streaming, solo agregamos los datos más recientes - this.addLatestDataToStreamingChart(sessionId, plotData); - } - - } catch (error) { - console.error(`Error refreshing streaming data for ${sessionId}:`, error); - } - } - - /** - * Actualiza los datos de streaming del chart - */ - updateStreamingData(sessionId, plotData) { - const sessionData = this.sessions.get(sessionId); - if (!sessionData || !sessionData.chart || !plotData.datasets) { - return; - } - - const chart = sessionData.chart; - - // Para cada dataset del backend, agregar los nuevos puntos - plotData.datasets.forEach((backendDataset, index) => { - if (chart.data.datasets[index] && backendDataset.data) { - const chartDataset = chart.data.datasets[index]; - - // Obtener los últimos puntos que no tengamos aún - const existingPoints = chartDataset.data.length; - const newPoints = backendDataset.data.slice(existingPoints); - - newPoints.forEach(point => { - window.ChartStreaming.addStreamingData(chart, index, { - x: point.x, - y: point.y - }); - }); - - plotDebugLog(`📈 Plot ${sessionId}: Added ${newPoints.length} new points to dataset ${index}`); - } - }); - } - - /** - * Agrega solo los datos más recientes al chart de streaming - */ - addLatestDataToStreamingChart(sessionId, plotData) { - const sessionData = this.sessions.get(sessionId); - if (!sessionData || !sessionData.chart || !plotData.datasets) { - return; - } - - const chart = sessionData.chart; - const now = Date.now(); - - // Para cada variable, agregar el punto más reciente - plotData.datasets.forEach((dataset, index) => { - if (dataset.data && dataset.data.length > 0) { - // Obtener el punto más reciente - const latestPoint = dataset.data[dataset.data.length - 1]; - - if (latestPoint && latestPoint.y !== null && latestPoint.y !== undefined) { - window.ChartStreaming.addStreamingData(chart, index, { - x: now, // Usar timestamp actual para streaming en tiempo real - y: latestPoint.y - }); - } - } - }); - } - - /** - * Controla la pausa/reanudación del streaming - */ - setStreamingPause(sessionId, paused) { - const sessionData = this.sessions.get(sessionId); - if (sessionData && sessionData.chart) { - window.ChartStreaming.setStreamingPause(sessionData.chart, paused); - plotDebugLog(`📈 Plot ${sessionId}: Streaming ${paused ? 'paused' : 'resumed'}`); - } - } - - /** - * Limpia todos los datos de streaming - */ - clearStreamingData(sessionId) { - const sessionData = this.sessions.get(sessionId); - if (sessionData && sessionData.chart) { - window.ChartStreaming.clearStreamingData(sessionData.chart); - plotDebugLog(`📈 Plot ${sessionId}: Streaming data cleared`); - } - } - - async createPlotSessionTab(sessionId, sessionInfo) { - // Crear tab dinámico para el plot - if (typeof tabManager !== 'undefined') { - tabManager.createPlotTab(sessionId, sessionInfo.name || `Plot ${sessionId}`); - } - - // 🚀 NUEVO: Obtener configuración completa del plot para el streaming - let plotConfig = sessionInfo; - try { - const configResponse = await fetch(`/api/plots/${sessionId}/config`); - const configData = await configResponse.json(); - if (configData.success && configData.config) { - plotConfig = configData.config; - } - } catch (error) { - console.warn(`Could not load plot config for ${sessionId}, using basic info`); - } - - // Crear el Chart.js con streaming en el canvas del tab - const ctx = document.getElementById(`chart-${sessionId}`).getContext('2d'); - - // Configurar streaming con la nueva librería - const streamingConfig = window.ChartStreaming.createStreamingChartConfig({ - duration: (plotConfig.time_window || 60) * 1000, // Convertir a milisegundos - refresh: 500, // Actualizar cada 500ms - frameRate: 30, - pause: !plotConfig.is_active, // Pausar si no está activo - yMin: plotConfig.y_min, - yMax: plotConfig.y_max, - onRefresh: (chart) => { - // Esta función se llama automáticamente para obtener nuevos datos - this.refreshStreamingData(sessionId, chart); - } - }); - - const chart = new Chart(ctx, streamingConfig); - - this.sessions.set(sessionId, { - chart: chart, - config: plotConfig, - lastDataFetch: 0, - datasets: new Map() // Para tracking de datasets por variable - }); - - // Inicializar datasets para las variables del plot - if (plotConfig.variables) { - this.initializeStreamingDatasets(sessionId, plotConfig); - } - - // Actualizar estadísticas del plot - this.updatePlotStats(sessionId, sessionInfo); - - console.log(`📈 Created streaming plot tab and chart for session: ${sessionId}`); - } - - updatePlotStats(sessionId, sessionInfo) { - const statsElement = document.getElementById(`plot-stats-${sessionId}`); - if (statsElement) { - const status = sessionInfo.is_active ? - (sessionInfo.is_paused ? 'Paused' : 'Active') : - 'Stopped'; - - statsElement.innerHTML = ` - Variables: ${sessionInfo.variables_count || 0} | - Data Points: 0 | - Status: ${status} | - ${sessionInfo.trigger_enabled ? `Trigger: ${sessionInfo.trigger_variable} (${sessionInfo.trigger_on_true ? 'True' : 'False'})` : 'No Trigger'} - `; - } - } - - async createNewPlot(config) { - try { - const response = await fetch('/api/plots', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(config) - }); - - const result = await response.json(); - - if (result.success) { - // 🔑 NUEVO: Obtener configuración real del backend en lugar de hardcodear - const configResponse = await fetch(`/api/plots/${result.session_id}/config`); - const configData = await configResponse.json(); - - let sessionConfig = { - name: config.name, - variables_count: config.variables.length, - trigger_enabled: config.trigger_enabled, - trigger_variable: config.trigger_variable, - trigger_on_true: config.trigger_on_true, - is_active: false, // Por defecto stopped hasta que se obtenga del backend - is_paused: false - }; - - // Si tenemos la configuración real del backend, usarla - if (configData.success && configData.config) { - sessionConfig = { - ...sessionConfig, - is_active: configData.config.is_active, - is_paused: configData.config.is_paused - }; - } - - await this.createPlotSessionTab(result.session_id, sessionConfig); - - // Cambiar al sub-tab del nuevo plot - if (typeof tabManager !== 'undefined') { - tabManager.switchSubTab(`plot-${result.session_id}`); - } - - // 🔑 NUEVO: Los plots se auto-inician en el backend, no necesitamos start manual aquí - console.log(`Plot session created and auto-started: ${result.session_id}`); - - showNotification(result.message, 'success'); - return result.session_id; - } else { - showNotification(result.error, 'error'); - return null; - } - } catch (error) { - console.error('Error creating new plot:', error); - showNotification('Error creating plot session', 'error'); - return null; - } - } - - initCollapsibleForm() { - const toggleBtn = document.getElementById('toggle-plot-form-btn'); - const formContainer = document.getElementById('plot-form-container'); - const closeBtn = document.getElementById('close-plot-form-btn'); - const cancelBtn = document.getElementById('cancel-plot-form'); - const plotForm = document.getElementById('plot-form'); - - if (toggleBtn) { - toggleBtn.addEventListener('click', () => { - this.showPlotForm(); - }); - } - - if (closeBtn) { - closeBtn.addEventListener('click', () => { - this.hidePlotForm(); - }); - } - - if (cancelBtn) { - cancelBtn.addEventListener('click', () => { - this.hidePlotForm(); - }); - } - - if (plotForm) { - plotForm.addEventListener('submit', (e) => { - e.preventDefault(); - this.handleFormSubmit(); - }); - } - - // Botón para abrir modal de variables - const selectVariablesBtn = document.getElementById('select-variables-btn'); - if (selectVariablesBtn) { - selectVariablesBtn.addEventListener('click', () => { - this.showVariableModal(); - }); - } - } - - initVariableModal() { - const modal = document.getElementById('variable-selection-modal'); - const closeBtn = document.getElementById('close-variable-modal'); - const cancelBtn = document.getElementById('cancel-variable-selection'); - const confirmBtn = document.getElementById('confirm-variable-selection'); - const selectAllBtn = document.getElementById('select-all-variables'); - const deselectAllBtn = document.getElementById('deselect-all-variables'); - - if (closeBtn) { - closeBtn.addEventListener('click', () => { - this.hideVariableModal(); - }); - } - - if (cancelBtn) { - cancelBtn.addEventListener('click', () => { - this.hideVariableModal(); - }); - } - - if (confirmBtn) { - confirmBtn.addEventListener('click', () => { - this.confirmVariableSelection(); - }); - } - - if (selectAllBtn) { - selectAllBtn.addEventListener('click', () => { - this.selectAllVisibleVariables(); - }); - } - - if (deselectAllBtn) { - deselectAllBtn.addEventListener('click', () => { - this.deselectAllVisibleVariables(); - }); - } - - // Prevenir cierre con teclas como Ctrl+V, Escape (opcional) - if (modal) { - modal.addEventListener('keydown', (e) => { - // Prevenir cierre accidental con Ctrl+V y otras combinaciones - if (e.ctrlKey || e.metaKey) { - e.stopPropagation(); - } - - // Solo permitir Escape para cerrar si se presiona intencionalmente - if (e.key === 'Escape' && !e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey) { - this.hideVariableModal(); - } - }); - - // Prevenir cierre al hacer clic en el modal (solo en el backdrop) - modal.addEventListener('click', (e) => { - if (e.target === modal) { - this.hideVariableModal(); - } - }); - } - } - - showPlotForm(sessionId = null) { - console.log('showPlotForm called with sessionId:', sessionId); - - // Asegurar que estemos en el tab de plotting - if (typeof tabManager !== 'undefined' && tabManager.getCurrentTab() !== 'plotting') { - console.log('Switching to plotting tab'); - tabManager.switchTab('plotting'); - } - - const formContainer = document.getElementById('plot-form-container'); - const formTitle = document.getElementById('plot-form-title'); - const submitBtn = document.getElementById('plot-form-submit'); - - // Verificar que los elementos existan - if (!formContainer) { - console.error('plot-form-container element not found'); - showNotification('Error: Form container not found', 'error'); - return; - } - - if (!formTitle) { - console.warn('plot-form-title element not found'); - } - - if (!submitBtn) { - console.warn('plot-form-submit element not found'); - } - - this.currentEditingSession = sessionId; - - if (sessionId) { - // Modo edición - console.log('Setting up form for editing'); - if (formTitle) formTitle.textContent = '✏️ Edit Plot'; - if (submitBtn) submitBtn.textContent = 'Update Plot'; - this.loadPlotConfigForEdit(sessionId); - } else { - // Modo creación - console.log('Setting up form for creation'); - if (formTitle) formTitle.textContent = '🆕 Create New Plot'; - if (submitBtn) submitBtn.textContent = 'Create Plot'; - this.resetPlotForm(); - } - - console.log('Making form visible'); - formContainer.style.display = 'block'; - - // Scroll hacia el formulario para que sea visible - setTimeout(() => { - formContainer.scrollIntoView({ behavior: 'smooth', block: 'start' }); - console.log('Scrolled to form'); - }, 100); - - // Cargar variables disponibles para triggers - this.loadTriggerVariables(); - - console.log('showPlotForm completed'); - } - - hidePlotForm() { - const formContainer = document.getElementById('plot-form-container'); - formContainer.style.display = 'none'; - this.currentEditingSession = null; - this.selectedVariables.clear(); - this.updateSelectedVariablesDisplay(); - } - - resetPlotForm() { - const form = document.getElementById('plot-form'); - form.reset(); - document.getElementById('plot-form-time-window').value = '60'; - document.getElementById('plot-form-trigger-on-true').checked = true; - this.selectedVariables.clear(); - this.updateSelectedVariablesDisplay(); - } - - async loadPlotConfigForEdit(sessionId) { - try { - const response = await fetch(`/api/plots/${sessionId}/config`); - const data = await response.json(); - - if (data.success && data.config) { - const config = data.config; - - // Llenar formulario - document.getElementById('plot-form-name').value = config.name || ''; - document.getElementById('plot-form-time-window').value = config.time_window || 60; - document.getElementById('plot-form-y-min').value = config.y_min || ''; - document.getElementById('plot-form-y-max').value = config.y_max || ''; - document.getElementById('plot-form-trigger-enabled').checked = config.trigger_enabled || false; - document.getElementById('plot-form-trigger-variable').value = config.trigger_variable || ''; - document.getElementById('plot-form-trigger-on-true').checked = config.trigger_on_true !== false; - - // Configurar trigger - this.togglePlotFormTriggerConfig(); - - // Cargar variables seleccionadas - this.selectedVariables.clear(); - if (config.variables) { - for (let i = 0; i < config.variables.length; i++) { - const variable = config.variables[i]; - const color = this.getColor(variable, i); - this.selectedVariables.set(variable, { - color: color, - dataset: 'Unknown' // Will be resolved when loading datasets - }); - } - } - this.updateSelectedVariablesDisplay(); - } - } catch (error) { - console.error('Error loading plot config for edit:', error); - showNotification('Error loading plot configuration', 'error'); - } - } - - async showVariableModal() { - const modal = document.getElementById('variable-selection-modal'); - - // Cargar datasets y variables - await this.loadDatasetsAndVariables(); - - modal.style.display = 'block'; - } - - hideVariableModal() { - const modal = document.getElementById('variable-selection-modal'); - modal.style.display = 'none'; - } - - async loadDatasetsAndVariables() { - try { - // Cargar datasets - const response = await fetch('/api/datasets'); - const data = await response.json(); - - if (data.success) { - this.availableDatasets = Object.entries(data.datasets).map(([id, dataset]) => ({ - id: id, - name: dataset.name, - variables: dataset.variables || {}, - active: data.active_datasets.includes(id) - })); - - this.updateDatasetsDisplay(); - } - } catch (error) { - console.error('Error loading datasets:', error); - showNotification('Error loading datasets', 'error'); - } - } - - updateDatasetsDisplay() { - const container = document.getElementById('datasets-list'); - container.innerHTML = ''; - - if (this.availableDatasets.length === 0) { - container.innerHTML = '

No datasets available

'; - return; - } - - for (const dataset of this.availableDatasets) { - const item = document.createElement('div'); - item.className = 'dataset-item'; - item.dataset.datasetId = dataset.id; - - const variableCount = Object.keys(dataset.variables).length; - const statusIcon = dataset.active ? '🟢' : '⚫'; - - item.innerHTML = ` - ${statusIcon} ${dataset.name} - ${variableCount} variables - `; - - item.addEventListener('click', () => { - this.selectDataset(dataset.id); - }); - - container.appendChild(item); - } - } - - selectDataset(datasetId) { - // Actualizar UI - document.querySelectorAll('.dataset-item').forEach(item => { - item.classList.remove('active'); - }); - document.querySelector(`[data-dataset-id="${datasetId}"]`).classList.add('active'); - - // Mostrar variables del dataset - const dataset = this.availableDatasets.find(d => d.id === datasetId); - if (dataset) { - this.updateVariablesDisplay(dataset); - } - } - - updateVariablesDisplay(dataset) { - const container = document.getElementById('variables-list'); - container.innerHTML = ''; - - const variables = Object.entries(dataset.variables); - - if (variables.length === 0) { - container.innerHTML = '

No variables in this dataset

'; - return; - } - - for (const [varName, varConfig] of variables) { - const item = document.createElement('div'); - item.className = 'variable-item'; - item.dataset.variableName = varName; - - const isSelected = this.selectedVariables.has(varName); - const selectedData = this.selectedVariables.get(varName); - const color = selectedData?.color || this.getNextAvailableColor(); - - if (isSelected) { - item.classList.add('selected'); - } - - // Formatear detalles de la variable - let details = `${varConfig.type.toUpperCase()}`; - if (varConfig.area === 'db') { - details += ` - DB${varConfig.db}.${varConfig.offset}`; - } else if (varConfig.area === 'mw') { - details += ` - MW${varConfig.offset}`; - } else if (['e', 'a', 'mb'].includes(varConfig.area)) { - details += ` - ${varConfig.area.toUpperCase()}${varConfig.offset}.${varConfig.bit || 0}`; - } - - item.innerHTML = ` -
-
${varName}
-
${details}
-
-
- - -
- `; - - container.appendChild(item); - } - } - - toggleVariableSelection(variableName, datasetId, selected) { - if (selected) { - const color = this.getNextAvailableColor(); - const datasetName = this.availableDatasets.find(d => d.id === datasetId)?.name || datasetId; - - this.selectedVariables.set(variableName, { - color: color, - dataset: datasetName - }); - - // Habilitar selector de color - const colorSelector = document.querySelector(`[data-variable-name="${variableName}"] .color-selector`); - if (colorSelector) { - colorSelector.disabled = false; - colorSelector.value = color; - } - - // Marcar item como seleccionado - const item = document.querySelector(`[data-variable-name="${variableName}"]`); - if (item) { - item.classList.add('selected'); - } - } else { - this.selectedVariables.delete(variableName); - - // Deshabilitar selector de color - const colorSelector = document.querySelector(`[data-variable-name="${variableName}"] .color-selector`); - if (colorSelector) { - colorSelector.disabled = true; - } - - // Desmarcar item - const item = document.querySelector(`[data-variable-name="${variableName}"]`); - if (item) { - item.classList.remove('selected'); - } - } - - this.updateSelectedSummary(); - } - - updateVariableColor(variableName, color) { - if (this.selectedVariables.has(variableName)) { - const data = this.selectedVariables.get(variableName); - data.color = color; - this.selectedVariables.set(variableName, data); - this.updateSelectedSummary(); - } - } - - selectAllVisibleVariables() { - const checkboxes = document.querySelectorAll('.variables-list .variable-checkbox'); - checkboxes.forEach(checkbox => { - if (!checkbox.checked) { - checkbox.checked = true; - checkbox.onchange(); - } - }); - } - - deselectAllVisibleVariables() { - const checkboxes = document.querySelectorAll('.variables-list .variable-checkbox'); - checkboxes.forEach(checkbox => { - if (checkbox.checked) { - checkbox.checked = false; - checkbox.onchange(); - } - }); - } - - updateSelectedSummary() { - const container = document.getElementById('selected-variables-summary'); - container.innerHTML = ''; - - if (this.selectedVariables.size === 0) { - container.innerHTML = '

No variables selected

'; - return; - } - - for (const [varName, data] of this.selectedVariables) { - const item = document.createElement('div'); - item.className = 'selected-summary-item'; - item.innerHTML = ` - - ${varName} - (${data.dataset}) - `; - container.appendChild(item); - } - } - - confirmVariableSelection() { - this.updateSelectedVariablesDisplay(); - this.hideVariableModal(); - } - - updateSelectedVariablesDisplay() { - const container = document.getElementById('selected-variables-display'); - container.innerHTML = ''; - - if (this.selectedVariables.size === 0) { - container.innerHTML = '

No variables selected

'; - return; - } - - for (const [varName, data] of this.selectedVariables) { - const chip = document.createElement('div'); - chip.className = 'variable-chip'; - chip.innerHTML = ` - - ${varName} - - `; - container.appendChild(chip); - } - } - - removeSelectedVariable(variableName) { - this.selectedVariables.delete(variableName); - this.updateSelectedVariablesDisplay(); - } - - getNextAvailableColor() { - const usedColors = new Set([...this.selectedVariables.values()].map(v => v.color)); - return this.colors.find(color => !usedColors.has(color)) || this.colors[0]; - } - - getColor(variable, index = null) { - if (index !== null) { - return this.colors[index % this.colors.length]; - } - const hash = this.hashCode(variable); - return this.colors[hash % this.colors.length]; - } - - hashCode(str) { - let hash = 0; - for (let i = 0; i < str.length; i++) { - const char = str.charCodeAt(i); - hash = ((hash << 5) - hash) + char; - hash = hash & hash; - } - return Math.abs(hash); - } - - async loadTriggerVariables() { - try { - const response = await fetch('/api/plots/variables'); - const data = await response.json(); - - const triggerSelect = document.getElementById('plot-form-trigger-variable'); - triggerSelect.innerHTML = ''; - - if (data.boolean_variables) { - for (const variable of data.boolean_variables) { - const option = document.createElement('option'); - option.value = variable; - option.textContent = variable; - triggerSelect.appendChild(option); - } - } - } catch (error) { - console.error('Error loading trigger variables:', error); - } - } - - async handleFormSubmit() { - if (this.selectedVariables.size === 0) { - showNotification('Please select at least one variable', 'error'); - return; - } - - const config = { - name: document.getElementById('plot-form-name').value || `Plot ${Date.now()}`, - variables: Array.from(this.selectedVariables.keys()), - time_window: parseInt(document.getElementById('plot-form-time-window').value) || 60, - y_min: document.getElementById('plot-form-y-min').value || null, - y_max: document.getElementById('plot-form-y-max').value || null, - trigger_enabled: document.getElementById('plot-form-trigger-enabled').checked, - trigger_variable: document.getElementById('plot-form-trigger-variable').value || null, - trigger_on_true: document.getElementById('plot-form-trigger-on-true').checked - }; - - // Convertir valores numéricos - if (config.y_min) config.y_min = parseFloat(config.y_min); - if (config.y_max) config.y_max = parseFloat(config.y_max); - - // 🔧 DEBUG: Log configuración del plot - plotDebugLog('📈 Creating plot with config:', config); - - try { - let response; - let sessionId = null; - - if (this.currentEditingSession) { - // Modo edición: ELIMINAR el plot existente y crear uno nuevo desde cero - console.log(`Deleting existing plot ${this.currentEditingSession} to recreate it from scratch`); - - // 1. Eliminar el plot existente - const deleteResponse = await fetch(`/api/plots/${this.currentEditingSession}`, { - method: 'DELETE' - }); - - if (!deleteResponse.ok) { - throw new Error('Failed to delete existing plot'); - } - - // 2. Remover de la UI - if (typeof tabManager !== 'undefined') { - tabManager.removePlotTab(this.currentEditingSession); - } - - // 3. Remover del PlotManager - if (this.sessions.has(this.currentEditingSession)) { - const chart = this.sessions.get(this.currentEditingSession); - if (chart) { - chart.destroy(); - } - this.sessions.delete(this.currentEditingSession); - } - - // 4. Crear nuevo plot desde cero - response = await fetch('/api/plots', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(config) - }); - - const deleteResult = await deleteResponse.json(); - console.log(`Old plot deleted: ${deleteResult.message || 'Success'}`); - } else { - // Modo creación normal - response = await fetch('/api/plots', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(config) - }); - } - - const result = await response.json(); - - if (result.success) { - sessionId = result.session_id; - - if (this.currentEditingSession) { - showNotification(`Plot recreated successfully: ${config.name}`, 'success'); - } else { - showNotification(result.message, 'success'); - } - - // Crear nuevo tab para la sesión (tanto en modo edición como creación) - await this.createPlotSessionTab(sessionId, { - name: config.name, - variables_count: config.variables.length, - trigger_enabled: config.trigger_enabled, - trigger_variable: config.trigger_variable, - trigger_on_true: config.trigger_on_true, - is_active: false, - is_paused: false - }); - - // Cambiar al sub-tab del nuevo plot - if (typeof tabManager !== 'undefined') { - tabManager.switchSubTab(`plot-${sessionId}`); - } - - this.hidePlotForm(); - } else { - showNotification(result.error, 'error'); - } - } catch (error) { - console.error('Error submitting plot form:', error); - if (this.currentEditingSession) { - showNotification('Error recreating plot. Please try again.', 'error'); - } else { - showNotification('Error creating plot', 'error'); - } - } - } - - destroy() { - this.stopAutoUpdate(); - - // Destruir todos los charts - for (const [sessionId, sessionData] of this.sessions) { - if (sessionData && sessionData.chart) { - sessionData.chart.destroy(); - } - } - this.sessions.clear(); - - console.log('📈 Plot Manager destroyed'); - } -} - -// Función global para toggle de trigger config -window.togglePlotFormTriggerConfig = function () { - const triggerEnabled = document.getElementById('plot-form-trigger-enabled'); - const triggerConfig = document.getElementById('plot-form-trigger-config'); - - if (triggerConfig) { - triggerConfig.style.display = triggerEnabled.checked ? 'block' : 'none'; - } -} - -// Función global corregida para editar plots -window.editPlotSession = function (sessionId) { - console.log('editPlotSession called with sessionId:', sessionId); - console.log('plotManager available:', !!plotManager); - console.log('tabManager available:', typeof tabManager !== 'undefined'); - - if (!sessionId || sessionId.trim() === '') { - console.error('Invalid or empty session ID provided to editPlotSession'); - showNotification('Error: Invalid session ID', 'error'); - return; - } - - if (!plotManager) { - console.error('Plot manager not initialized'); - showNotification('Error: Plot manager not available', 'error'); - return; - } - - console.log('Opening plot form for editing session:', sessionId); - console.log('Current tab:', typeof tabManager !== 'undefined' ? tabManager.getCurrentTab() : 'tabManager not available'); - - plotManager.showPlotForm(sessionId); - - console.log('Plot form should now be visible'); -} - -// Función global para remover sesiones de plot -window.removePlotSession = async function (sessionId) { - if (confirm('¿Estás seguro de que quieres eliminar este plot?')) { - try { - const response = await fetch(`/api/plots/${sessionId}`, { - method: 'DELETE' - }); - - const result = await response.json(); - - if (result.success) { - // Remover tab dinámico - if (typeof tabManager !== 'undefined') { - tabManager.removePlotTab(sessionId); - } - - // Remover de PlotManager - if (plotManager && plotManager.sessions.has(sessionId)) { - plotManager.sessions.delete(sessionId); - } - - showNotification(result.message, 'success'); - } else { - showNotification(result.error, 'error'); - } - } catch (error) { - console.error('Error removing plot:', error); - showNotification('Error removing plot session', 'error'); - } - } -} - -// Función de utilidad para notificaciones -window.showNotification = function (message, type = 'info') { - console.log(`${type.toUpperCase()}: ${message}`); - - // Si tienes un sistema de notificaciones, úsalo aquí - if (typeof showAlert === 'function') { - showAlert(message, type); - } else if (typeof showMessage === 'function') { - showMessage(message, type); - } -} - -// Inicialización -let plotManager = null; - -document.addEventListener('DOMContentLoaded', function () { - // Inicializar Plot Manager - plotManager = new PlotManager(); - - // Cerrar modales con Escape (pero prevenir cierre accidental) - document.addEventListener('keydown', function (e) { - if (e.key === 'Escape' && !e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey) { - // Cerrar formulario colapsable si está abierto - const formContainer = document.getElementById('plot-form-container'); - if (formContainer && formContainer.style.display !== 'none') { - plotManager.hidePlotForm(); - } - } - }); -}); \ No newline at end of file diff --git a/web_test/PLC S7-315 Streamer & Logger_files/status.js.descargar b/web_test/PLC S7-315 Streamer & Logger_files/status.js.descargar deleted file mode 100644 index e214d23..0000000 --- a/web_test/PLC S7-315 Streamer & Logger_files/status.js.descargar +++ /dev/null @@ -1,256 +0,0 @@ -/** - * Gestión del estado del sistema y actualizaciones en tiempo real - */ - -// Variables para el streaming de estado -let statusEventSource = null; -let isStreamingStatus = false; - -// Actualizar el estado del sistema -function updateStatus() { - fetch('/api/status') - .then(response => response.json()) - .then(data => { - const plcStatus = document.getElementById('plc-status'); - const streamStatus = document.getElementById('stream-status'); - const csvStatus = document.getElementById('csv-status'); - const diskSpaceStatus = document.getElementById('disk-space'); - - // Actualizar estado de conexión PLC - if (data.plc_connected) { - plcStatus.innerHTML = '🔌 PLC: Connected
'; - plcStatus.className = 'status-item status-connected'; - - // Añadir event listener al nuevo botón de desconexión - document.getElementById('status-disconnect-btn').addEventListener('click', function () { - fetch('/api/plc/disconnect', { method: 'POST' }) - .then(response => response.json()) - .then(data => { - showMessage(data.message, data.success ? 'success' : 'error'); - updateStatus(); - }); - }); - } else { - plcStatus.innerHTML = '🔌 PLC: Disconnected
'; - plcStatus.className = 'status-item status-disconnected'; - - // Añadir event listener al botón de conexión - document.getElementById('status-connect-btn').addEventListener('click', function () { - fetch('/api/plc/connect', { method: 'POST' }) - .then(response => response.json()) - .then(data => { - showMessage(data.message, data.success ? 'success' : 'error'); - updateStatus(); - }); - }); - } - - // Actualizar estado de streaming UDP - if (data.streaming) { - streamStatus.innerHTML = '📡 UDP Streaming: Active
'; - streamStatus.className = 'status-item status-streaming'; - - // Añadir event listener al botón de parar streaming UDP - document.getElementById('status-streaming-btn').addEventListener('click', function () { - fetch('/api/udp/streaming/stop', { method: 'POST' }) - .then(response => response.json()) - .then(data => { - showMessage(data.message, data.success ? 'success' : 'error'); - updateStatus(); - }); - }); - } else { - streamStatus.innerHTML = '📡 UDP Streaming: Inactive
'; - streamStatus.className = 'status-item status-idle'; - - // Añadir event listener al botón de iniciar streaming UDP - document.getElementById('status-start-btn').addEventListener('click', function () { - fetch('/api/udp/streaming/start', { method: 'POST' }) - .then(response => response.json()) - .then(data => { - showMessage(data.message, data.success ? 'success' : 'error'); - updateStatus(); - }); - }); - } - - // Actualizar estado de grabación CSV - if (data.csv_recording) { - csvStatus.textContent = `💾 CSV: Recording`; - csvStatus.className = 'status-item status-streaming'; - } else { - csvStatus.textContent = `💾 CSV: Inactive`; - csvStatus.className = 'status-item status-idle'; - } - - // Actualizar estado de espacio en disco - if (data.disk_space_info) { - diskSpaceStatus.innerHTML = `💽 Disk: ${data.disk_space_info.free_space} free
- ⏱️ ~${data.disk_space_info.recording_time_left}`; - diskSpaceStatus.className = 'status-item status-idle'; - } else { - diskSpaceStatus.textContent = '💽 Disk Space: Calculating...'; - diskSpaceStatus.className = 'status-item status-idle'; - } - }) - .catch(error => console.error('Error updating status:', error)); -} - -// Iniciar streaming de estado en tiempo real -function startStatusStreaming() { - if (isStreamingStatus) { - return; - } - - // Cerrar conexión existente si hay alguna - if (statusEventSource) { - statusEventSource.close(); - } - - // Crear nueva conexión EventSource - statusEventSource = new EventSource('/api/stream/status?interval=2.0'); - - statusEventSource.onopen = function (event) { - console.log('Status streaming connected'); - isStreamingStatus = true; - }; - - statusEventSource.onmessage = function (event) { - try { - const data = JSON.parse(event.data); - - switch (data.type) { - case 'connected': - console.log('Status stream connected:', data.message); - break; - - case 'status': - // Actualizar estado en tiempo real - updateStatusFromStream(data.status); - break; - - case 'error': - console.error('Status stream error:', data.message); - break; - } - } catch (error) { - console.error('Error parsing status SSE data:', error); - } - }; - - statusEventSource.onerror = function (event) { - console.error('Status stream error:', event); - isStreamingStatus = false; - - // Intentar reconectar después de un retraso - setTimeout(() => { - startStatusStreaming(); - }, 10000); - }; -} - -// Detener streaming de estado en tiempo real -function stopStatusStreaming() { - if (statusEventSource) { - statusEventSource.close(); - statusEventSource = null; - } - isStreamingStatus = false; -} - -// Actualizar estado desde datos de streaming -function updateStatusFromStream(status) { - const plcStatus = document.getElementById('plc-status'); - const streamStatus = document.getElementById('stream-status'); - const csvStatus = document.getElementById('csv-status'); - const diskSpaceStatus = document.getElementById('disk-space'); - - // Actualizar estado de conexión PLC - if (status.plc_connected) { - plcStatus.innerHTML = '🔌 PLC: Connected
'; - plcStatus.className = 'status-item status-connected'; - - // Añadir event listener al nuevo botón de desconexión - const disconnectBtn = document.getElementById('status-disconnect-btn'); - if (disconnectBtn) { - disconnectBtn.addEventListener('click', function () { - fetch('/api/plc/disconnect', { method: 'POST' }) - .then(response => response.json()) - .then(data => { - showMessage(data.message, data.success ? 'success' : 'error'); - updateStatus(); - }); - }); - } - } else { - plcStatus.innerHTML = '🔌 PLC: Disconnected
'; - plcStatus.className = 'status-item status-disconnected'; - - // Añadir event listener al botón de conexión - const connectBtn = document.getElementById('status-connect-btn'); - if (connectBtn) { - connectBtn.addEventListener('click', function () { - fetch('/api/plc/connect', { method: 'POST' }) - .then(response => response.json()) - .then(data => { - showMessage(data.message, data.success ? 'success' : 'error'); - updateStatus(); - }); - }); - } - } - - // Actualizar estado de streaming UDP - if (status.streaming) { - streamStatus.innerHTML = '📡 UDP Streaming: Active
'; - streamStatus.className = 'status-item status-streaming'; - - // Añadir event listener al botón de parar streaming UDP - const stopBtn = document.getElementById('status-streaming-btn'); - if (stopBtn) { - stopBtn.addEventListener('click', function () { - fetch('/api/udp/streaming/stop', { method: 'POST' }) - .then(response => response.json()) - .then(data => { - showMessage(data.message, data.success ? 'success' : 'error'); - updateStatus(); - }); - }); - } - } else { - streamStatus.innerHTML = '📡 UDP Streaming: Inactive
'; - streamStatus.className = 'status-item status-idle'; - - // Añadir event listener al botón de iniciar streaming UDP - const startBtn = document.getElementById('status-start-btn'); - if (startBtn) { - startBtn.addEventListener('click', function () { - fetch('/api/udp/streaming/start', { method: 'POST' }) - .then(response => response.json()) - .then(data => { - showMessage(data.message, data.success ? 'success' : 'error'); - updateStatus(); - }); - }); - } - } - - // Actualizar estado de grabación CSV - if (status.csv_recording) { - csvStatus.textContent = `💾 CSV: Recording`; - csvStatus.className = 'status-item status-streaming'; - } else { - csvStatus.textContent = `💾 CSV: Inactive`; - csvStatus.className = 'status-item status-idle'; - } - - // Actualizar estado de espacio en disco - if (status.disk_space_info) { - diskSpaceStatus.innerHTML = `💽 Disk: ${status.disk_space_info.free_space} free
- ⏱️ ~${status.disk_space_info.recording_time_left}`; - diskSpaceStatus.className = 'status-item status-idle'; - } else { - diskSpaceStatus.textContent = '💽 Disk Space: Calculating...'; - diskSpaceStatus.className = 'status-item status-idle'; - } -} \ No newline at end of file diff --git a/web_test/PLC S7-315 Streamer & Logger_files/streaming.js.descargar b/web_test/PLC S7-315 Streamer & Logger_files/streaming.js.descargar deleted file mode 100644 index 40373be..0000000 --- a/web_test/PLC S7-315 Streamer & Logger_files/streaming.js.descargar +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Gestión del streaming UDP a PlotJuggler (independiente del recording CSV) - */ - -// Inicializar listeners para el control de streaming UDP -function initStreamingListeners() { - // Iniciar streaming UDP - document.getElementById('start-streaming-btn').addEventListener('click', function () { - fetch('/api/udp/streaming/start', { method: 'POST' }) - .then(response => response.json()) - .then(data => { - showMessage(data.message, data.success ? 'success' : 'error'); - updateStatus(); - }) - .catch(error => { - console.error('Error starting UDP streaming:', error); - showMessage('Error starting UDP streaming', 'error'); - }); - }); - - // Detener streaming UDP - document.getElementById('stop-streaming-btn').addEventListener('click', function () { - fetch('/api/udp/streaming/stop', { method: 'POST' }) - .then(response => response.json()) - .then(data => { - showMessage(data.message, data.success ? 'success' : 'error'); - updateStatus(); - }) - .catch(error => { - console.error('Error stopping UDP streaming:', error); - showMessage('Error stopping UDP streaming', 'error'); - }); - }); - - // Cargar estado de streaming de variables - loadStreamingStatus(); -} - -// Cargar estado de variables en streaming -function loadStreamingStatus() { - fetch('/api/variables/streaming') - .then(response => response.json()) - .then(data => { - if (data.success) { - data.streaming_variables.forEach(varName => { - const checkbox = document.getElementById(`stream-${varName}`); - if (checkbox) { - checkbox.checked = true; - } - }); - } - }) - .catch(error => console.error('Error loading streaming status:', error)); -} \ No newline at end of file diff --git a/web_test/PLC S7-315 Streamer & Logger_files/styles.css b/web_test/PLC S7-315 Streamer & Logger_files/styles.css deleted file mode 100644 index d0ed8fc..0000000 --- a/web_test/PLC S7-315 Streamer & Logger_files/styles.css +++ /dev/null @@ -1,1281 +0,0 @@ -/* Header with logo */ -.header { - text-align: center; - margin-bottom: 2rem; -} - -.header h1 { - display: flex; - align-items: center; - justify-content: center; - gap: 1rem; - flex-wrap: wrap; -} - -.header-logo { - height: 1.2em; - width: auto; - vertical-align: middle; -} - -/* Status grid */ -.status-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 1rem; - margin-bottom: 2rem; -} - -.status-item { - padding: 1rem; - border-radius: var(--pico-border-radius); - text-align: center; - font-weight: bold; - background: var(--pico-card-background-color); - border: var(--pico-border-width) solid var(--pico-border-color); -} - -.status-connected { - background: var(--pico-primary-background); - color: var(--pico-primary-inverse); -} - -.status-disconnected { - background: var(--pico-secondary-background); - color: var(--pico-secondary-inverse); -} - -.status-streaming { - background: var(--pico-primary-background); - color: var(--pico-primary-inverse); -} - -.status-idle { - background: var(--pico-muted-background-color); - color: var(--pico-muted-color); -} - -/* Configuration grid */ -.config-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); - gap: 1.5rem; - margin-bottom: 1.5rem; -} - -/* Form styling */ -.form-row { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 1rem; -} - -.controls { - display: flex; - flex-wrap: wrap; - gap: 0.5rem; - margin-top: 1rem; -} - -/* Variables table */ -.variables-table { - width: 100%; - margin-top: 1rem; -} - -/* Variable value cells styling */ -.variables-table td[id^="value-"] { - font-family: var(--pico-font-family-monospace); - font-weight: bold; - text-align: center; - min-width: 100px; -} - -/* Refresh button styling */ -#refresh-values-btn:disabled { - opacity: 0.6; - cursor: not-allowed; -} - -/* Diagnose button styling */ -#diagnose-btn:disabled { - opacity: 0.6; - cursor: not-allowed; -} - -/* Last refresh time styling */ -#last-refresh-time { - font-style: italic; - font-size: 0.85rem; -} - -/* Error cell tooltips */ -.variables-table td[id^="value-"]:hover { - position: relative; -} - -/* Variable value status colors */ -.value-success { - color: var(--pico-color-green-600) !important; -} - -.value-error { - color: var(--pico-color-red-500) !important; -} - -.value-warning { - color: var(--pico-color-amber-600) !important; -} - -.value-offline { - color: var(--pico-muted-color) !important; -} - -/* Alert styles */ -.alert { - padding: 1rem; - border-radius: var(--pico-border-radius); - margin: 1rem 0; - font-weight: bold; -} - -.alert-success { - background-color: var(--pico-primary-background); - color: var(--pico-primary-inverse); -} - -.alert-error { - background-color: var(--pico-color-red-400); - color: white; -} - -.alert-warning { - background-color: var(--pico-color-amber-100); - color: var(--pico-color-amber-800); - border: 1px solid var(--pico-color-amber-300); -} - -.alert-info { - background-color: var(--pico-color-blue-100); - color: var(--pico-color-blue-800); - border: 1px solid var(--pico-color-blue-300); -} - -/* Dataset controls */ -.dataset-controls { - background: var(--pico-card-background-color); - border: var(--pico-border-width) solid var(--pico-border-color); - padding: 1.5rem; - border-radius: var(--pico-border-radius); - margin-bottom: 1.5rem; -} - -/* Info section */ -.info-section { - background: var(--pico-muted-background-color); - border: var(--pico-border-width) solid var(--pico-muted-border-color); - border-radius: var(--pico-border-radius); - padding: 1.5rem; - margin-bottom: 1.5rem; -} - -.info-section p { - margin: 0.5rem 0; -} - -/* Log container */ -.log-container { - max-height: 400px; - overflow-y: auto; - background: var(--pico-background-color); - border: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - padding: 1rem; - font-family: var(--pico-font-family-monospace); -} - -.log-entry { - display: flex; - flex-direction: column; - margin-bottom: 1rem; - padding: 0.75rem; - border-radius: var(--pico-border-radius); - font-size: 0.875rem; - border-left: 3px solid transparent; -} - -.log-entry.log-info { - background: var(--pico-card-background-color); - border-left-color: var(--pico-primary); -} - -.log-entry.log-warning { - background: var(--pico-color-amber-50); - border-left-color: var(--pico-color-amber-500); -} - -.log-entry.log-error { - background: var(--pico-color-red-50); - border-left-color: var(--pico-color-red-500); -} - -.log-header { - display: flex; - justify-content: space-between; - align-items: center; - font-weight: bold; - margin-bottom: 0.25rem; -} - -.log-timestamp { - font-size: 0.75rem; - opacity: 0.7; -} - -.log-message { - margin-bottom: 0.25rem; - word-wrap: break-word; -} - -.log-details { - font-size: 0.75rem; - opacity: 0.8; - background: rgba(0, 0, 0, 0.1); - padding: 0.5rem; - border-radius: var(--pico-border-radius); - margin-top: 0.25rem; - white-space: pre-wrap; -} - -.log-controls { - display: flex; - gap: 1rem; - margin-bottom: 1rem; - flex-wrap: wrap; - align-items: center; -} - -.log-stats { - background: var(--pico-muted-background-color); - border: var(--pico-border-width) solid var(--pico-muted-border-color); - border-radius: var(--pico-border-radius); - padding: 0.5rem 1rem; - font-size: 0.875rem; -} - -/* Utility classes */ -.status-active { - color: var(--pico-primary); - font-weight: bold; -} - -.status-inactive { - color: var(--pico-muted-color); -} - -.status-error { - color: var(--pico-color-red-500); -} - -/* Modal improvements */ -.modal { - position: fixed; - z-index: 1000; - left: 0; - top: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.5); - backdrop-filter: blur(5px); -} - -.modal article { - margin: 5% auto; - width: 90%; - max-width: 600px; -} - -.modal header { - display: flex; - justify-content: space-between; - align-items: center; -} - -.close { - font-size: 1.5rem; - font-weight: bold; - cursor: pointer; - border: none; - background: none; - color: inherit; -} - -.close:hover { - opacity: 0.7; -} - -/* Mobile responsive */ -@media (max-width: 768px) { - .header h1 { - flex-direction: column; - gap: 0.5rem; - } - - .header-logo { - height: 1.2em; - } - - .config-grid { - grid-template-columns: 1fr; - } - - .form-row { - grid-template-columns: 1fr; - } - - .controls { - flex-direction: column; - } - - .status-grid { - grid-template-columns: 1fr; - } -} - -/* Theme selector */ -.theme-selector { - position: fixed; - top: 1rem; - right: 1rem; - z-index: 1000; - background: var(--pico-card-background-color); - border: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - padding: 0.5rem; - display: flex; - gap: 0.5rem; - align-items: center; - box-shadow: var(--pico-box-shadow); -} - -.theme-selector button { - padding: 0.25rem 0.5rem; - font-size: 0.75rem; - border-radius: var(--pico-border-radius); - cursor: pointer; - transition: all 0.2s ease; -} - -.theme-selector button.active { - background: var(--pico-primary-background); - color: var(--pico-primary-inverse); -} - -.theme-selector button:not(.active) { - background: var(--pico-muted-background-color); - color: var(--pico-muted-color); -} - -.theme-selector button:hover:not(.active) { - background: var(--pico-primary-hover); - color: var(--pico-primary-inverse); -} - -/* Font size reduction - 15% smaller (more balanced) */ -html { - font-size: 85%; - /* 15% reduction from 100% - more balanced */ -} - -/* Adjust specific elements that might need fine-tuning */ -.header h1 { - font-size: 2.2rem; - /* Adjusted for smaller base font */ -} - -.header p { - font-size: 1.1rem; -} - -.status-item { - font-size: 1rem; -} - -.log-entry { - font-size: 0.9rem; - /* Adjusted for smaller base font */ -} - -.log-timestamp { - font-size: 0.8rem; - /* Adjusted for smaller base font */ -} - -.log-details { - font-size: 0.8rem; - /* Adjusted for smaller base font */ -} - -.log-stats { - font-size: 0.9rem; - /* Adjusted for smaller base font */ -} - -/* Ensure buttons and inputs remain readable */ -button, -input, -select, -textarea { - font-size: 1rem; -} - -/* Table adjustments */ -.variables-table th, -.variables-table td { - font-size: 0.95rem; -} - -/* Modal adjustments */ -.modal article { - font-size: 1rem; -} - -.modal h3 { - font-size: 1.4rem; -} - -/* CSV Configuration styles */ -.csv-config-display { - background: var(--pico-card-background-color); - border: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - padding: 1.5rem; - margin-bottom: 1rem; -} - -.config-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); - gap: 1rem; -} - -.config-item { - display: flex; - justify-content: space-between; - align-items: center; - padding: 0.5rem 0; - border-bottom: 1px solid var(--pico-muted-border-color); -} - -.config-item:last-child { - border-bottom: none; -} - -.config-item span { - font-weight: normal; - color: var(--pico-primary); -} - -.directory-stats { - padding: 1rem; - background: var(--pico-muted-background-color); - border-radius: var(--pico-border-radius); - margin-top: 0.5rem; -} - -.directory-stats .stat-item { - display: flex; - justify-content: space-between; - margin: 0.5rem 0; -} - -.day-folder-item { - background: var(--pico-card-background-color); - border: 1px solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - padding: 0.75rem; - margin: 0.5rem 0; - display: flex; - justify-content: space-between; - align-items: center; -} - -.cleanup-status { - padding: 0.5rem; - border-radius: var(--pico-border-radius); - margin: 0.5rem 0; - font-weight: bold; -} - -.cleanup-success { - background-color: var(--pico-color-green-100); - color: var(--pico-color-green-800); - border: 1px solid var(--pico-color-green-300); -} - -.cleanup-error { - background-color: var(--pico-color-red-100); - color: var(--pico-color-red-800); - border: 1px solid var(--pico-color-red-300); -} - -/* 📈 PLOT SYSTEM STYLES */ -.plot-session { - background: var(--pico-card-background-color); - border: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - margin-bottom: 1rem; - overflow: hidden; -} - -.plot-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 0.75rem 1rem; - background: var(--pico-muted-background-color); - border-bottom: var(--pico-border-width) solid var(--pico-border-color); -} - -.plot-header h4 { - margin: 0; - color: var(--pico-h4-color); - font-size: 1.1rem; -} - -.plot-controls { - display: flex; - gap: 0.5rem; - flex-wrap: wrap; -} - -.plot-controls .btn { - padding: 0.25rem 0.5rem; - font-size: 0.8rem; - min-width: auto; -} - -.plot-info { - padding: 0.5rem 1rem; - background: var(--pico-card-background-color); - border-bottom: var(--pico-border-width) solid var(--pico-border-color); - font-size: 0.85rem; - color: var(--pico-muted-color); -} - -.plot-stats { - display: flex; - gap: 1rem; - flex-wrap: wrap; -} - -.plot-canvas { - padding: 1rem; - height: 300px; - position: relative; -} - -.plot-canvas canvas { - max-height: 100%; -} - -/* Modal de creación de plots */ -.modal { - display: none; - position: fixed; - z-index: 1000; - left: 0; - top: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.5); -} - -.modal-content { - background-color: var(--pico-card-background-color); - margin: 5% auto; - padding: 2rem; - border: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - width: 90%; - max-width: 600px; - max-height: 80vh; - overflow-y: auto; -} - -.modal-content h4 { - margin-top: 0; - color: var(--pico-h4-color); -} - -.form-group { - margin-bottom: 1rem; -} - -.form-group label { - display: block; - margin-bottom: 0.5rem; - font-weight: bold; - color: var(--pico-form-element-label-color); -} - -.form-group input, -.form-group select { - width: 100%; - padding: 0.5rem; - border: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - background: var(--pico-form-element-background-color); - color: var(--pico-form-element-color); -} - -.form-group input:focus, -.form-group select:focus { - border-color: var(--pico-primary); - outline: none; -} - -.variable-checkbox { - margin-bottom: 0.5rem; -} - -.variable-checkbox label { - display: flex; - align-items: center; - gap: 0.5rem; - font-weight: normal; - cursor: pointer; -} - -.variable-checkbox input[type="checkbox"] { - width: auto; - margin: 0; -} - -.range-inputs { - display: flex; - align-items: center; - gap: 0.5rem; -} - -.range-inputs input { - flex: 1; -} - -.range-inputs span { - color: var(--pico-muted-color); - font-weight: bold; -} - -.form-actions { - display: flex; - gap: 1rem; - justify-content: flex-end; - margin-top: 2rem; - padding-top: 1rem; - border-top: var(--pico-border-width) solid var(--pico-border-color); -} - -.form-actions .btn { - min-width: 100px; -} - -#trigger-config { - padding: 1rem; - background: var(--pico-muted-background-color); - border: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - margin-top: 0.5rem; -} - -#trigger-config .form-group { - margin-bottom: 0.5rem; -} - -#trigger-config .form-group:last-child { - margin-bottom: 0; -} - -/* Responsive design para plots */ -@media (max-width: 768px) { - .plot-header { - flex-direction: column; - align-items: flex-start; - gap: 0.5rem; - } - - .plot-controls { - width: 100%; - justify-content: flex-start; - } - - .plot-stats { - flex-direction: column; - gap: 0.25rem; - } - - .modal-content { - margin: 10% auto; - width: 95%; - padding: 1rem; - } - - .form-actions { - flex-direction: column; - } - - .range-inputs { - flex-direction: column; - align-items: stretch; - } -} - -/* TAB SYSTEM STYLES */ -.tabs { - display: flex; - border-bottom: var(--pico-border-width) solid var(--pico-border-color); - margin-bottom: 1.5rem; - background: var(--pico-card-background-color); - border-radius: var(--pico-border-radius) var(--pico-border-radius) 0 0; - overflow-x: auto; -} - -.tab-btn { - padding: 1rem 1.5rem; - border: none; - background: none; - color: var(--pico-muted-color); - cursor: pointer; - font-weight: 500; - border-bottom: 3px solid transparent; - transition: all 0.2s ease; - white-space: nowrap; - flex: 1; - text-align: center; - min-width: 0; - max-width: none; -} - -.tab-btn:hover { - color: var(--pico-primary); - background: var(--pico-muted-background-color); -} - -.tab-btn.active { - color: var(--pico-primary); - border-bottom-color: var(--pico-primary); - background: var(--pico-card-background-color); -} - -/* Plot tabs específicos */ -.tab-btn.plot-tab { - position: relative; - padding-right: 2.5rem; -} - -.tab-close { - position: absolute; - right: 0.5rem; - top: 50%; - transform: translateY(-50%); - background: none; - border: none; - color: var(--pico-muted-color); - cursor: pointer; - font-size: 1.2rem; - line-height: 1; - padding: 0.25rem; - border-radius: 50%; - transition: all 0.2s ease; -} - -.tab-close:hover { - background: var(--pico-muted-background-color); - color: var(--pico-color-red-500); -} - -/* SUB-TABS STYLES */ -.sub-tabs { - display: flex; - border-bottom: var(--pico-border-width) solid var(--pico-border-color); - margin-bottom: 1rem; - background: var(--pico-muted-background-color); - border-radius: var(--pico-border-radius); - overflow-x: auto; -} - -.sub-tab-btn { - padding: 0.75rem 1rem; - border: none; - background: none; - color: var(--pico-muted-color); - cursor: pointer; - font-weight: 500; - border-bottom: 2px solid transparent; - transition: all 0.2s ease; - white-space: nowrap; - flex: 1; - text-align: center; - min-width: 0; - font-size: 0.9rem; -} - -.sub-tab-btn:hover { - color: var(--pico-primary); - background: var(--pico-card-background-color); -} - -.sub-tab-btn.active { - color: var(--pico-primary); - border-bottom-color: var(--pico-primary); - background: var(--pico-card-background-color); -} - -.sub-tab-btn.plot-sub-tab { - position: relative; - padding-right: 2rem; -} - -.sub-tab-close { - position: absolute; - right: 0.25rem; - top: 50%; - transform: translateY(-50%); - background: none; - border: none; - color: var(--pico-muted-color); - cursor: pointer; - font-size: 1rem; - line-height: 1; - padding: 0.2rem; - border-radius: 50%; - transition: all 0.2s ease; -} - -.sub-tab-close:hover { - background: var(--pico-muted-background-color); - color: var(--pico-color-red-500); -} - -.sub-tab-content { - display: none; -} - -.sub-tab-content.active { - display: block; -} - -.tab-content { - display: none; -} - -.tab-content.active { - display: block; -} - -/* COLLAPSIBLE PLOT FORM STYLES */ -.collapsible-section { - margin-bottom: 1.5rem; - border: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - overflow: hidden; - transition: all 0.3s ease; -} - -.plot-form-article { - margin: 0; - background: var(--pico-card-background-color); -} - -.plot-form-article header { - background: var(--pico-primary-background); - color: var(--pico-primary-inverse); - padding: 1rem 1.5rem; - margin: 0; -} - -.close-btn { - background: none; - border: none; - color: inherit; - font-size: 1.5rem; - cursor: pointer; - padding: 0.25rem; - border-radius: 50%; - transition: all 0.2s ease; -} - -.close-btn:hover { - background: rgba(255, 255, 255, 0.2); -} - -.variables-selection { - border: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - padding: 1rem; - background: var(--pico-muted-background-color); -} - -.selected-variables { - margin-bottom: 1rem; - min-height: 2rem; - padding: 0.5rem; - border: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - background: var(--pico-card-background-color); -} - -.selected-variables .no-variables { - color: var(--pico-muted-color); - font-style: italic; - margin: 0; -} - -.variable-chip { - display: inline-flex; - align-items: center; - gap: 0.5rem; - padding: 0.25rem 0.75rem; - margin: 0.25rem; - background: var(--pico-primary-background); - color: var(--pico-primary-inverse); - border-radius: var(--pico-border-radius); - font-size: 0.875rem; - font-weight: 500; -} - -.variable-chip .color-indicator { - width: 12px; - height: 12px; - border-radius: 50%; - border: 1px solid rgba(255, 255, 255, 0.3); -} - -.variable-chip .remove-variable { - background: none; - border: none; - color: inherit; - cursor: pointer; - font-size: 1.1rem; - padding: 0; - margin-left: 0.25rem; - opacity: 0.8; - transition: opacity 0.2s ease; -} - -.variable-chip .remove-variable:hover { - opacity: 1; -} - -/* VARIABLE SELECTION MODAL STYLES */ -.variable-modal { - max-width: 900px; - width: 95%; - max-height: 85vh; - display: flex; - flex-direction: column; -} - -.modal-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 1.5rem; - background: var(--pico-primary-background); - color: var(--pico-primary-inverse); - margin: 0; - border-radius: var(--pico-border-radius) var(--pico-border-radius) 0 0; -} - -.modal-header h3 { - margin: 0; - color: inherit; -} - -.modal-body { - flex: 1; - padding: 1.5rem; - overflow-y: auto; -} - -.modal-footer { - padding: 1rem 1.5rem; - background: var(--pico-muted-background-color); - border-top: var(--pico-border-width) solid var(--pico-border-color); - display: flex; - justify-content: flex-end; - gap: 1rem; -} - -.variable-selection-container { - display: grid; - grid-template-columns: 200px 1fr; - gap: 1.5rem; - margin-bottom: 1.5rem; -} - -.datasets-sidebar { - border: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - padding: 1rem; - background: var(--pico-muted-background-color); - height: fit-content; -} - -.datasets-sidebar h4 { - margin: 0 0 1rem 0; - color: var(--pico-h4-color); - font-size: 1rem; -} - -.datasets-list { - display: flex; - flex-direction: column; - gap: 0.5rem; -} - -.dataset-item { - padding: 0.75rem; - border: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - background: var(--pico-card-background-color); - cursor: pointer; - transition: all 0.2s ease; - font-size: 0.875rem; -} - -.dataset-item:hover { - background: var(--pico-primary-hover); - color: var(--pico-primary-inverse); -} - -.dataset-item.active { - background: var(--pico-primary-background); - color: var(--pico-primary-inverse); -} - -.dataset-item .dataset-name { - font-weight: bold; - display: block; -} - -.dataset-item .dataset-info { - font-size: 0.75rem; - opacity: 0.8; - margin-top: 0.25rem; -} - -.variables-main { - border: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - background: var(--pico-card-background-color); -} - -.variables-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 1rem; - background: var(--pico-muted-background-color); - border-bottom: var(--pico-border-width) solid var(--pico-border-color); -} - -.variables-header h4 { - margin: 0; - color: var(--pico-h4-color); - font-size: 1rem; -} - -.selection-controls { - display: flex; - gap: 0.5rem; -} - -.selection-controls .btn { - padding: 0.25rem 0.75rem; - font-size: 0.75rem; -} - -.variables-list { - padding: 1rem; - max-height: 300px; - overflow-y: auto; -} - -.variable-item { - display: flex; - align-items: center; - justify-content: space-between; - padding: 0.75rem; - margin-bottom: 0.5rem; - border: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - background: var(--pico-muted-background-color); - transition: all 0.2s ease; -} - -.variable-item:last-child { - margin-bottom: 0; -} - -.variable-item.selected { - background: var(--pico-primary-background); - color: var(--pico-primary-inverse); - border-color: var(--pico-primary); -} - -.variable-info { - flex: 1; -} - -.variable-name { - font-weight: bold; - margin-bottom: 0.25rem; -} - -.variable-details { - font-size: 0.75rem; - opacity: 0.8; -} - -.variable-controls { - display: flex; - align-items: center; - gap: 0.75rem; -} - -.variable-checkbox { - transform: scale(1.2); -} - -.color-selector { - width: 40px; - height: 30px; - border: none; - border-radius: var(--pico-border-radius); - cursor: pointer; - transition: all 0.2s ease; -} - -.color-selector:hover { - transform: scale(1.1); -} - -.color-selector:disabled { - opacity: 0.5; - cursor: not-allowed; - transform: none; -} - -.selected-summary { - border: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - padding: 1rem; - background: var(--pico-muted-background-color); -} - -.selected-summary h4 { - margin: 0 0 1rem 0; - color: var(--pico-h4-color); - font-size: 1rem; -} - -.selected-summary-list { - display: flex; - flex-wrap: wrap; - gap: 0.5rem; - min-height: 2rem; -} - -.selected-summary-item { - display: flex; - align-items: center; - gap: 0.5rem; - padding: 0.5rem 0.75rem; - background: var(--pico-card-background-color); - border: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - font-size: 0.875rem; -} - -.selected-summary-item .color-indicator { - width: 16px; - height: 16px; - border-radius: 50%; - border: 1px solid var(--pico-border-color); -} - -.no-dataset-message { - text-align: center; - color: var(--pico-muted-color); - font-style: italic; - padding: 2rem; - margin: 0; -} - -/* Responsive design para variable modal */ -@media (max-width: 768px) { - .variable-modal { - width: 98%; - max-height: 90vh; - } - - .variable-selection-container { - grid-template-columns: 1fr; - gap: 1rem; - } - - .datasets-sidebar { - max-height: 150px; - overflow-y: auto; - } - - .variables-header { - flex-direction: column; - align-items: flex-start; - gap: 0.5rem; - } - - .selection-controls { - width: 100%; - justify-content: flex-end; - } - - .variable-item { - flex-direction: column; - align-items: flex-start; - gap: 0.5rem; - } - - .variable-controls { - width: 100%; - justify-content: flex-end; - } - - .modal-footer { - flex-direction: column; - gap: 0.5rem; - } -} - -/* Responsive tabs */ -@media (max-width: 768px) { - .tabs { - flex-direction: column; - border-bottom: none; - border-right: var(--pico-border-width) solid var(--pico-border-color); - border-radius: var(--pico-border-radius) 0 0 var(--pico-border-radius); - margin-bottom: 1rem; - } - - .tab-btn { - border-bottom: none; - border-right: 3px solid transparent; - text-align: left; - min-width: auto; - } - - .tab-btn.active { - border-right-color: var(--pico-primary); - } -} \ No newline at end of file diff --git a/web_test/PLC S7-315 Streamer & Logger_files/tabs.js.descargar b/web_test/PLC S7-315 Streamer & Logger_files/tabs.js.descargar deleted file mode 100644 index d746059..0000000 --- a/web_test/PLC S7-315 Streamer & Logger_files/tabs.js.descargar +++ /dev/null @@ -1,296 +0,0 @@ -/** - * Tab System Management - * Maneja la navegación entre tabs en la aplicación - */ - -class TabManager { - constructor() { - this.currentTab = 'datasets'; - this.plotTabs = new Set(); // Track dynamic plot tabs - this.init(); - } - - init() { - // Event listeners para los botones de tab estáticos - this.bindStaticTabs(); - - // Inicializar con el tab activo por defecto - this.switchTab(this.currentTab); - - console.log('📑 Tab Manager initialized'); - } - - bindStaticTabs() { - // Solo bindear tabs estáticos, los dinámicos se bindean al crearlos - document.querySelectorAll('.tab-btn:not([data-plot-id])').forEach(btn => { - btn.addEventListener('click', (e) => { - const tabName = e.target.dataset.tab; - this.switchTab(tabName); - }); - }); - } - - switchTab(tabName) { - // Remover clase active de todos los tabs - document.querySelectorAll('.tab-btn').forEach(btn => { - btn.classList.remove('active'); - }); - - document.querySelectorAll('.tab-content').forEach(content => { - content.classList.remove('active'); - }); - - // Activar el tab seleccionado - const activeBtn = document.querySelector(`[data-tab="${tabName}"]`); - const activeContent = document.getElementById(`${tabName}-tab`); - - if (activeBtn && activeContent) { - activeBtn.classList.add('active'); - activeContent.classList.add('active'); - this.currentTab = tabName; - - // Eventos específicos por tab - this.handleTabSpecificEvents(tabName); - - console.log(`📑 Switched to tab: ${tabName}`); - } - } - - createPlotTab(sessionId, plotName) { - // Crear botón de sub-tab dinámico - const subTabBtn = document.createElement('button'); - subTabBtn.className = 'sub-tab-btn plot-sub-tab'; - subTabBtn.dataset.subTab = `plot-${sessionId}`; - subTabBtn.dataset.plotId = sessionId; - subTabBtn.innerHTML = ` - 📈 ${plotName} - × - `; - - // Crear contenido del sub-tab - const subTabContent = document.createElement('div'); - subTabContent.className = 'sub-tab-content plot-sub-tab-content'; - subTabContent.id = `plot-${sessionId}-sub-tab`; - subTabContent.innerHTML = ` -
-
-
- 📈 ${plotName} -
- - -
-
-
- -
-
-

📈 ${plotName}

-
- - - - -
-
-
- - Loading plot information... - -
-
- -
-
-
- `; - - // Mostrar sub-tabs si no están visibles - const subTabs = document.getElementById('plot-sub-tabs'); - const plotSessionsContainer = document.getElementById('plot-sessions-container'); - const plotSubContent = document.getElementById('plot-sub-content'); - - if (subTabs.style.display === 'none') { - subTabs.style.display = 'flex'; - plotSessionsContainer.style.display = 'none'; - plotSubContent.style.display = 'block'; - } - - // Agregar sub-tab al contenedor de sub-tabs - subTabs.appendChild(subTabBtn); - - // Agregar contenido del sub-tab - plotSubContent.appendChild(subTabContent); - - // Bind events - subTabBtn.addEventListener('click', (e) => { - if (!e.target.classList.contains('sub-tab-close')) { - this.switchSubTab(`plot-${sessionId}`); - } - }); - - // Close button event - subTabBtn.querySelector('.sub-tab-close').addEventListener('click', (e) => { - e.stopPropagation(); - // Llamar a la función que elimina el plot del backend Y del frontend - if (typeof window.removePlotSession === 'function') { - window.removePlotSession(sessionId); - } else { - console.error('removePlotSession function not available'); - // Fallback: solo remover del frontend - this.removePlotTab(sessionId); - } - }); - - this.plotTabs.add(sessionId); - - console.log(`📑 Created plot sub-tab for session: ${sessionId}`); - return subTabBtn; - } - - switchSubTab(subTabName) { - // Remover clase active de todos los sub-tabs - document.querySelectorAll('.sub-tab-btn').forEach(btn => { - btn.classList.remove('active'); - }); - - document.querySelectorAll('.sub-tab-content').forEach(content => { - content.classList.remove('active'); - }); - - // Activar el sub-tab seleccionado - const activeBtn = document.querySelector(`[data-sub-tab="${subTabName}"]`); - const activeContent = document.getElementById(`${subTabName}-sub-tab`); - - if (activeBtn && activeContent) { - activeBtn.classList.add('active'); - activeContent.classList.add('active'); - - // Eventos específicos por sub-tab - this.handleSubTabSpecificEvents(subTabName); - - console.log(`📑 Switched to sub-tab: ${subTabName}`); - } - } - - handleSubTabSpecificEvents(subTabName) { - if (subTabName.startsWith('plot-')) { - // Sub-tab de plot individual - cargar datos específicos - const sessionId = subTabName.replace('plot-', ''); - if (typeof plotManager !== 'undefined') { - plotManager.updateSessionData(sessionId); - } - } - } - - removePlotTab(sessionId) { - // Remover sub-tab button - const subTabBtn = document.querySelector(`[data-plot-id="${sessionId}"]`); - if (subTabBtn) { - subTabBtn.remove(); - } - - // Remover sub-tab content - const subTabContent = document.getElementById(`plot-${sessionId}-sub-tab`); - if (subTabContent) { - subTabContent.remove(); - } - - this.plotTabs.delete(sessionId); - - // Si no quedan sub-tabs, mostrar vista inicial - const subTabs = document.getElementById('plot-sub-tabs'); - const plotSessionsContainer = document.getElementById('plot-sessions-container'); - const plotSubContent = document.getElementById('plot-sub-content'); - - if (subTabs.children.length === 0) { - subTabs.style.display = 'none'; - plotSessionsContainer.style.display = 'block'; - plotSubContent.style.display = 'none'; - } - - console.log(`📑 Removed plot sub-tab for session: ${sessionId}`); - } - - updatePlotTabName(sessionId, newName) { - const subTabBtn = document.querySelector(`[data-plot-id="${sessionId}"]`); - if (subTabBtn) { - subTabBtn.innerHTML = ` - 📈 ${newName} - × - `; - - // Re-bind close event - subTabBtn.querySelector('.sub-tab-close').addEventListener('click', (e) => { - e.stopPropagation(); - // Llamar a la función que elimina el plot del backend Y del frontend - if (typeof window.removePlotSession === 'function') { - window.removePlotSession(sessionId); - } else { - console.error('removePlotSession function not available'); - // Fallback: solo remover del frontend - this.removePlotTab(sessionId); - } - }); - } - - // Actualizar header del contenido - const header = document.querySelector(`#plot-${sessionId}-sub-tab h4`); - if (header) { - header.textContent = `📈 ${newName}`; - } - - const articleHeader = document.querySelector(`#plot-${sessionId}-sub-tab header span`); - if (articleHeader) { - articleHeader.textContent = `📈 ${newName}`; - } - } - - handleTabSpecificEvents(tabName) { - switch (tabName) { - case 'plotting': - // Inicializar plotting si no está inicializado - if (typeof plotManager !== 'undefined' && !plotManager.isInitialized) { - plotManager.init(); - } - break; - - case 'events': - // Cargar eventos si no están cargados - if (typeof loadEvents === 'function') { - loadEvents(); - } - break; - - case 'datasets': - // Actualizar datasets si es necesario - if (typeof loadDatasets === 'function') { - loadDatasets(); - } - break; - } - } - - getCurrentTab() { - return this.currentTab; - } -} - -// Inicialización -let tabManager = null; - -document.addEventListener('DOMContentLoaded', function () { - tabManager = new TabManager(); -}); \ No newline at end of file diff --git a/web_test/PLC S7-315 Streamer & Logger_files/theme.js.descargar b/web_test/PLC S7-315 Streamer & Logger_files/theme.js.descargar deleted file mode 100644 index 3bb556c..0000000 --- a/web_test/PLC S7-315 Streamer & Logger_files/theme.js.descargar +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Gestión del tema de la aplicación (claro/oscuro/auto) - */ - -// Establecer el tema -function setTheme(theme) { - const html = document.documentElement; - const buttons = document.querySelectorAll('.theme-selector button'); - - // Eliminar clase active de todos los botones - buttons.forEach(btn => btn.classList.remove('active')); - - // Establecer tema - html.setAttribute('data-theme', theme); - - // Añadir clase active al botón seleccionado - document.getElementById(`theme-${theme}`).classList.add('active'); - - // Guardar preferencia en localStorage - localStorage.setItem('theme', theme); -} - -// Cargar tema guardado al cargar la página -function loadTheme() { - const savedTheme = localStorage.getItem('theme') || 'light'; - setTheme(savedTheme); -} - -// Inicializar tema al cargar la página -document.addEventListener('DOMContentLoaded', function () { - loadTheme(); -}); \ No newline at end of file diff --git a/web_test/PLC S7-315 Streamer & Logger_files/utils.js.descargar b/web_test/PLC S7-315 Streamer & Logger_files/utils.js.descargar deleted file mode 100644 index 8f9014a..0000000 --- a/web_test/PLC S7-315 Streamer & Logger_files/utils.js.descargar +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Funciones de utilidad general para la aplicación - */ - -// Función para mostrar mensajes en la interfaz -function showMessage(message, type = 'success') { - const messagesDiv = document.getElementById('messages'); - let alertClass; - - switch (type) { - case 'success': - alertClass = 'alert-success'; - break; - case 'warning': - alertClass = 'alert-warning'; - break; - case 'info': - alertClass = 'alert-info'; - break; - case 'error': - default: - alertClass = 'alert-error'; - break; - } - - messagesDiv.innerHTML = `
${message}
`; - setTimeout(() => { - messagesDiv.innerHTML = ''; - }, 5000); -} - -// Formatear timestamp para los logs -function formatTimestamp(isoString) { - const date = new Date(isoString); - return date.toLocaleString('es-ES', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - second: '2-digit' - }); -} - -// Obtener icono para tipo de evento -function getEventIcon(eventType) { - const icons = { - 'plc_connection': '🔗', - 'plc_connection_failed': '❌', - 'plc_disconnection': '🔌', - 'plc_disconnection_error': '⚠️', - 'streaming_started': '▶️', - 'streaming_stopped': '⏹️', - 'streaming_error': '❌', - 'csv_started': '💾', - 'csv_stopped': '📁', - 'csv_error': '❌', - 'config_change': '⚙️', - 'variable_added': '➕', - 'variable_removed': '➖', - 'application_started': '🚀' - }; - return icons[eventType] || '📋'; -} \ No newline at end of file diff --git a/web_test/PLC S7-315 Streamer & Logger_files/variables.js.descargar b/web_test/PLC S7-315 Streamer & Logger_files/variables.js.descargar deleted file mode 100644 index 66b99a3..0000000 --- a/web_test/PLC S7-315 Streamer & Logger_files/variables.js.descargar +++ /dev/null @@ -1,322 +0,0 @@ -/** - * Gestión de variables y streaming de valores en tiempo real - */ - -// Variables para el streaming de variables -let variableEventSource = null; -let isStreamingVariables = false; - -// Toggle de campos de variables según el área de memoria -function toggleFields() { - const area = document.getElementById('var-area').value; - const dbField = document.getElementById('db-field'); - const dbInput = document.getElementById('var-db'); - const bitField = document.getElementById('bit-field'); - const typeSelect = document.getElementById('var-type'); - - // Manejar campo DB - if (area === 'db') { - dbField.style.display = 'block'; - dbInput.required = true; - } else { - dbField.style.display = 'none'; - dbInput.required = false; - dbInput.value = 1; // Valor por defecto para áreas no DB - } - - // Manejar campo Bit y restricciones de tipo de datos - if (area === 'e' || area === 'a' || area === 'mb') { - bitField.style.display = 'block'; - // Para áreas de bit, forzar tipo de dato a bool - typeSelect.value = 'bool'; - // Deshabilitar otros tipos de datos para áreas de bit - Array.from(typeSelect.options).forEach(option => { - option.disabled = (option.value !== 'bool'); - }); - } else { - bitField.style.display = 'none'; - // Re-habilitar todos los tipos de datos para áreas no-bit - Array.from(typeSelect.options).forEach(option => { - option.disabled = false; - }); - } -} - -// Toggle de campos de edición de variables -function toggleEditFields() { - const area = document.getElementById('edit-var-area').value; - const dbField = document.getElementById('edit-db-field'); - const dbInput = document.getElementById('edit-var-db'); - const bitField = document.getElementById('edit-bit-field'); - const typeSelect = document.getElementById('edit-var-type'); - - // Manejar campo DB - if (area === 'db') { - dbField.style.display = 'block'; - dbInput.required = true; - } else { - dbField.style.display = 'none'; - dbInput.required = false; - dbInput.value = 1; // Valor por defecto para áreas no DB - } - - // Manejar campo Bit y restricciones de tipo de datos - if (area === 'e' || area === 'a' || area === 'mb') { - bitField.style.display = 'block'; - // Para áreas de bit, forzar tipo de dato a bool - typeSelect.value = 'bool'; - // Deshabilitar otros tipos de datos para áreas de bit - Array.from(typeSelect.options).forEach(option => { - option.disabled = (option.value !== 'bool'); - }); - } else { - bitField.style.display = 'none'; - // Re-habilitar todos los tipos de datos para áreas no-bit - Array.from(typeSelect.options).forEach(option => { - option.disabled = false; - }); - } -} - -// Actualizar streaming para una variable -function toggleStreaming(varName, enabled) { - if (!currentDatasetId) { - showMessage('No dataset selected', 'error'); - return; - } - - fetch(`/api/datasets/${currentDatasetId}/variables/${varName}/streaming`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ enabled: enabled }) - }) - .then(response => response.json()) - .then(data => { - showMessage(data.message, data.success ? 'success' : 'error'); - updateStatus(); // Actualizar contador de variables de streaming - }) - .catch(error => { - console.error('Error toggling streaming:', error); - showMessage('Error updating streaming setting', 'error'); - }); -} - -// Auto-start live display when dataset changes (if PLC is connected) -function autoStartLiveDisplay() { - if (currentDatasetId) { - // Check if PLC is connected by fetching status - fetch('/api/status') - .then(response => response.json()) - .then(status => { - if (status.plc_connected && !isStreamingVariables) { - startVariableStreaming(); - showMessage('Live display started automatically for active dataset', 'info'); - } - }) - .catch(error => { - console.error('Error checking PLC status:', error); - }); - } -} - -// Limpiar todos los valores de variables y establecer mensaje de estado -function clearVariableValues(statusMessage = '--') { - // Encontrar todas las celdas de valor y limpiarlas - const valueCells = document.querySelectorAll('[id^="value-"]'); - valueCells.forEach(cell => { - cell.textContent = statusMessage; - cell.style.color = 'var(--pico-muted-color)'; - }); -} - - - -// Iniciar streaming de variables en tiempo real -function startVariableStreaming() { - if (!currentDatasetId || isStreamingVariables) { - return; - } - - // Cerrar conexión existente si hay alguna - if (variableEventSource) { - variableEventSource.close(); - } - - // Crear nueva conexión EventSource - variableEventSource = new EventSource(`/api/stream/variables?dataset_id=${currentDatasetId}&interval=1.0`); - - variableEventSource.onopen = function (event) { - console.log('Variable streaming connected'); - isStreamingVariables = true; - updateStreamingIndicator(true); - }; - - variableEventSource.onmessage = function (event) { - try { - const data = JSON.parse(event.data); - - switch (data.type) { - case 'connected': - console.log('Variable stream connected:', data.message); - break; - - case 'values': - // Actualizar valores de variables en tiempo real desde caché - updateVariableValuesFromStream(data); - break; - - case 'cache_error': - console.error('Cache error in variable stream:', data.message); - showMessage(`Cache error: ${data.message}`, 'error'); - clearVariableValues('CACHE ERROR'); - break; - - case 'plc_disconnected': - clearVariableValues('PLC OFFLINE'); - showMessage('PLC disconnected - cache not being populated', 'warning'); - break; - - case 'dataset_inactive': - clearVariableValues('DATASET INACTIVE'); - showMessage('Dataset is not active - activate to populate cache', 'warning'); - break; - - case 'no_variables': - clearVariableValues('NO VARIABLES'); - showMessage('No variables defined in this dataset', 'info'); - break; - - case 'no_cache': - clearVariableValues('READING...'); - const samplingInfo = data.sampling_interval ? ` (every ${data.sampling_interval}s)` : ''; - showMessage(`Waiting for cache to be populated${samplingInfo}`, 'info'); - break; - - case 'stream_error': - console.error('SSE stream error:', data.message); - showMessage(`Streaming error: ${data.message}`, 'error'); - break; - - default: - console.warn('Unknown SSE message type:', data.type); - break; - } - } catch (error) { - console.error('Error parsing SSE data:', error); - } - }; - - variableEventSource.onerror = function (event) { - console.error('Variable stream error:', event); - isStreamingVariables = false; - updateStreamingIndicator(false); - - // Intentar reconectar después de un retraso - setTimeout(() => { - if (currentDatasetId) { - startVariableStreaming(); - } - }, 5000); - }; -} - -// Detener streaming de variables en tiempo real -function stopVariableStreaming() { - if (variableEventSource) { - variableEventSource.close(); - variableEventSource = null; - } - isStreamingVariables = false; - updateStreamingIndicator(false); -} - -// Actualizar valores de variables desde datos de streaming -function updateVariableValuesFromStream(data) { - const values = data.values; - const timestamp = data.timestamp; - const source = data.source; - const stats = data.stats; - - // Actualizar cada valor de variable - Object.keys(values).forEach(varName => { - const valueCell = document.getElementById(`value-${varName}`); - if (valueCell) { - const value = values[varName]; - valueCell.textContent = value; - - // Código de color basado en el estado del valor - if (value === 'ERROR' || value === 'FORMAT_ERROR') { - valueCell.style.color = 'var(--pico-color-red-500)'; - valueCell.style.fontWeight = 'bold'; - } else { - valueCell.style.color = 'var(--pico-color-green-600)'; - valueCell.style.fontWeight = 'bold'; - } - } - }); - - // Actualizar timestamp e información de origen - const lastRefreshTime = document.getElementById('last-refresh-time'); - if (lastRefreshTime) { - const sourceIcon = source === 'cache' ? '📊' : '🔗'; - const sourceText = source === 'cache' ? 'streaming cache' : 'direct PLC'; - - if (stats && stats.failed > 0) { - lastRefreshTime.innerHTML = ` - 🔄 Live streaming
- - ⚠️ ${stats.success}/${stats.total} variables (${stats.failed} failed) -
- - ${sourceIcon} ${sourceText} • ${new Date(timestamp).toLocaleTimeString()} - - `; - } else { - lastRefreshTime.innerHTML = ` - 🔄 Live streaming
- - ✅ All ${stats ? stats.success : 'N/A'} variables OK -
- - ${sourceIcon} ${sourceText} • ${new Date(timestamp).toLocaleTimeString()} - - `; - } - } -} - -// Actualizar indicador de streaming -function updateStreamingIndicator(isStreaming) { - const toggleBtn = document.getElementById('toggle-streaming-btn'); - if (toggleBtn) { - if (isStreaming) { - toggleBtn.innerHTML = '⏹️ Stop Live Display'; - toggleBtn.title = 'Stop live variable display'; - } else { - toggleBtn.innerHTML = '▶️ Start Live Display'; - toggleBtn.title = 'Start live variable display'; - } - } -} - -// Alternar streaming en tiempo real -function toggleRealTimeStreaming() { - if (isStreamingVariables) { - stopVariableStreaming(); - showMessage('Real-time streaming stopped', 'info'); - } else { - startVariableStreaming(); - showMessage('Real-time streaming started', 'success'); - } - - // Actualizar texto del botón - const toggleBtn = document.getElementById('toggle-streaming-btn'); - if (toggleBtn) { - if (isStreamingVariables) { - toggleBtn.innerHTML = '⏹️ Stop Live Streaming'; - } else { - toggleBtn.innerHTML = '▶️ Start Live Streaming'; - } - } -} - diff --git a/web_test/consola.txt b/web_test/consola.txt deleted file mode 100644 index 4f5e6f1..0000000 --- a/web_test/consola.txt +++ /dev/null @@ -1,856 +0,0 @@ -plotting.js:20 📈 Plot plot_16: Updating streaming chart with 1 datasets -plotting.js:20 - Variable 1: UR29_Brix (31 points) -plotting.js:20 📈 Plot plot_16: Added 0 new points to dataset 0 -debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING -debug-streaming.js:9 ============================================================ -debug-streaming.js:23 -1️⃣ VERIFICANDO LIBRERÍAS... -debug-streaming.js:30 Chart.js: ✅ -debug-streaming.js:31 ChartStreaming: ✅ -debug-streaming.js:32 PlotManager: ✅ -debug-streaming.js:41 -2️⃣ VERIFICANDO PLOT MANAGER... -debug-streaming.js:44 ✅ PlotManager inicializado -debug-streaming.js:45 📊 Sesiones activas: 1 -debug-streaming.js:59 -3️⃣ ANALIZANDO SESIÓN DE PLOT... -debug-streaming.js:65 📈 Sesión encontrada: plot_16 -debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …} -debug-streaming.js:85 ✅ Escala realtime configurada correctamente -debug-streaming.js:93 ✅ Streaming habilitado en el chart -debug-streaming.js:98 -4️⃣ VERIFICANDO DATOS DEL BACKEND... -plotting.js:9 📈 Plot debugging enabled. Check console for detailed logs. -debug-streaming.js:278 ⚡ TEST RÁPIDO DE STREAMING -debug-streaming.js:279 ------------------------------ -debug-streaming.js:288 plotManager: ✅ true -debug-streaming.js:288 sessions: ✅ 1 -debug-streaming.js:288 chartStreaming: ✅ true -debug-streaming.js:293 -🧪 Probando sesión: plot_16 -debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING -debug-streaming.js:9 ============================================================ -debug-streaming.js:23 -1️⃣ VERIFICANDO LIBRERÍAS... -debug-streaming.js:30 Chart.js: ✅ -debug-streaming.js:31 ChartStreaming: ✅ -debug-streaming.js:32 PlotManager: ✅ -debug-streaming.js:41 -2️⃣ VERIFICANDO PLOT MANAGER... -debug-streaming.js:44 ✅ PlotManager inicializado -debug-streaming.js:45 📊 Sesiones activas: 1 -debug-streaming.js:59 -3️⃣ ANALIZANDO SESIÓN DE PLOT... -debug-streaming.js:65 📈 Sesión encontrada: plot_16 -debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …} -debug-streaming.js:85 ✅ Escala realtime configurada correctamente -debug-streaming.js:93 ✅ Streaming habilitado en el chart -debug-streaming.js:98 -4️⃣ VERIFICANDO DATOS DEL BACKEND... -chartjs-plugin-streaming.js:75 📈 RealTimeScale DEBUG - scaleOptions: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …} -chartjs-plugin-streaming.js:76 📈 RealTimeScale DEBUG - me.options: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …} -chartjs-plugin-streaming.js:77 📈 RealTimeScale DEBUG - me.options.realtime: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …} -plotting.js:20 📈 Plot plot_16: Fetching data from backend... -chartjs-plugin-streaming.js:81 📈 RealTimeScale DEBUG - onRefresh resolved: null object -chartjs-plugin-streaming.js:93 📈 RealTimeScale initialized: {duration: 60000, refresh: 500, pause: false, hasOnRefresh: false} -debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING -debug-streaming.js:9 ============================================================ -debug-streaming.js:23 -1️⃣ VERIFICANDO LIBRERÍAS... -debug-streaming.js:30 Chart.js: ✅ -debug-streaming.js:31 ChartStreaming: ✅ -debug-streaming.js:32 PlotManager: ✅ -debug-streaming.js:41 -2️⃣ VERIFICANDO PLOT MANAGER... -debug-streaming.js:44 ✅ PlotManager inicializado -debug-streaming.js:45 📊 Sesiones activas: 1 - -3️⃣ ANALIZANDO SESIÓN DE PLOT... -debug-streaming.js:65 📈 Sesión encontrada: plot_16 -debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …} -debug-streaming.js:85 ✅ Escala realtime configurada correctamente -debug-streaming.js:93 ✅ Streaming habilitado en el chart -debug-streaming.js:98 -4️⃣ VERIFICANDO DATOS DEL BACKEND... -plotting.js:9 📈 Plot debugging enabled. Check console for detailed logs. -debug-streaming.js:278 ⚡ TEST RÁPIDO DE STREAMING -debug-streaming.js:279 ------------------------------ -debug-streaming.js:288 plotManager: ✅ true -debug-streaming.js:288 sessions: ✅ 1 -debug-streaming.js:288 chartStreaming: ✅ true -debug-streaming.js:293 -🧪 Probando sesión: plot_16 -debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING -debug-streaming.js:9 ============================================================ -debug-streaming.js:23 -1️⃣ VERIFICANDO LIBRERÍAS... -debug-streaming.js:30 Chart.js: ✅ -debug-streaming.js:31 ChartStreaming: ✅ -debug-streaming.js:32 PlotManager: ✅ -debug-streaming.js:41 -2️⃣ VERIFICANDO PLOT MANAGER... -debug-streaming.js:44 ✅ PlotManager inicializado -debug-streaming.js:45 📊 Sesiones activas: 1 -debug-streaming.js:59 -3️⃣ ANALIZANDO SESIÓN DE PLOT... -debug-streaming.js:65 📈 Sesión encontrada: plot_16 -debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …} -debug-streaming.js:85 ✅ Escala realtime configurada correctamente -debug-streaming.js:93 ✅ Streaming habilitado en el chart -debug-streaming.js:98 -4️⃣ VERIFICANDO DATOS DEL BACKEND... -debug-streaming.js:103 📊 Respuesta del backend: {success: true, datasets: 1, totalPoints: 32, isActive: true, isPaused: false} -debug-streaming.js:112 ✅ Backend devuelve datos -debug-streaming.js:116 📈 Primer dataset: {label: 'UR29_Brix', dataPoints: 32, samplePoint: {…}} -debug-streaming.js:123 ✅ Dataset tiene puntos de datos -debug-streaming.js:130 ⏰ Análisis de timestamps: {firstPointTime: 1753115880879.299, currentTime: 1753115887307, differenceMs: 6427.700927734375, differenceSec: 6, isRecent: true} -debug-streaming.js:156 -5️⃣ PROBANDO FUNCIONALIDAD STREAMING... -chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115887307, y=67.77492245225156 -debug-streaming.js:165 🧪 Test de addStreamingData: ✅ -debug-streaming.js:170 📊 Puntos después del test: 38 -debug-streaming.js:182 -6️⃣ RESUMEN DEL DIAGNÓSTICO -debug-streaming.js:183 ======================================== -debug-streaming.js:186 🎉 No se encontraron errores graves -debug-streaming.js:199 -7️⃣ PRÓXIMOS PASOS RECOMENDADOS: -debug-streaming.js:213 🔧 4. Si persiste: plotManager.controlPlot("plot_16", "stop") y luego "start" -plotting.js:20 📈 Plot plot_16: Streaming resumed - 🔧 DIAGNÓSTICO DETALLADO DE STREAMING - ============================================================ - -1️⃣ VERIFICANDO LIBRERÍAS... - Chart.js: ✅ - ChartStreaming: ✅ - PlotManager: ✅ - -2️⃣ VERIFICANDO PLOT MANAGER... - ✅ PlotManager inicializado - 📊 Sesiones activas: 1 - -3️⃣ ANALIZANDO SESIÓN DE PLOT... - 📈 Sesión encontrada: plot_16 - Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …} - ✅ Escala realtime configurada correctamente - ✅ Streaming habilitado en el chart - -4️⃣ VERIFICANDO DATOS DEL BACKEND... -plotting.js:9 📈 Plot debugging enabled. Check console for detailed logs. -debug-streaming.js:278 ⚡ TEST RÁPIDO DE STREAMING -debug-streaming.js:279 ------------------------------ -debug-streaming.js:288 plotManager: ✅ true -debug-streaming.js:288 sessions: ✅ 1 -debug-streaming.js:288 chartStreaming: ✅ true -debug-streaming.js:293 -🧪 Probando sesión: plot_16 -debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING -debug-streaming.js:9 ============================================================ -debug-streaming.js:23 -1️⃣ VERIFICANDO LIBRERÍAS... -debug-streaming.js:30 Chart.js: ✅ -debug-streaming.js:31 ChartStreaming: ✅ -debug-streaming.js:32 PlotManager: ✅ -debug-streaming.js:41 -2️⃣ VERIFICANDO PLOT MANAGER... -debug-streaming.js:44 ✅ PlotManager inicializado -debug-streaming.js:45 📊 Sesiones activas: 1 -debug-streaming.js:59 -3️⃣ ANALIZANDO SESIÓN DE PLOT... -debug-streaming.js:65 📈 Sesión encontrada: plot_16 -debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …} -debug-streaming.js:85 ✅ Escala realtime configurada correctamente -debug-streaming.js:93 ✅ Streaming habilitado en el chart -debug-streaming.js:98 -4️⃣ VERIFICANDO DATOS DEL BACKEND... -chartjs-plugin-streaming.js:75 📈 RealTimeScale DEBUG - scaleOptions: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …} -chartjs-plugin-streaming.js:76 📈 RealTimeScale DEBUG - me.options: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …} -chartjs-plugin-streaming.js:77 📈 RealTimeScale DEBUG - me.options.realtime: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …} -chartjs-plugin-streaming.js:81 📈 RealTimeScale DEBUG - onRefresh resolved: null object -chartjs-plugin-streaming.js:93 📈 RealTimeScale initialized: {duration: 60000, refresh: 500, pause: false, hasOnRefresh: false} -debug-streaming.js:103 📊 Respuesta del backend: {success: true, datasets: 1, totalPoints: 34, isActive: true, isPaused: false} -debug-streaming.js:112 ✅ Backend devuelve datos -debug-streaming.js:116 📈 Primer dataset: {label: 'UR29_Brix', dataPoints: 34, samplePoint: {…}} -debug-streaming.js:123 ✅ Dataset tiene puntos de datos -debug-streaming.js:130 ⏰ Análisis de timestamps: {firstPointTime: 1753115880879.299, currentTime: 1753115887617, differenceMs: 6737.700927734375, differenceSec: 7, isRecent: true} -debug-streaming.js:156 -5️⃣ PROBANDO FUNCIONALIDAD STREAMING... -chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115887617, y=11.743880180299548 -debug-streaming.js:165 🧪 Test de addStreamingData: ✅ -debug-streaming.js:170 📊 Puntos después del test: 39 -debug-streaming.js:182 -6️⃣ RESUMEN DEL DIAGNÓSTICO -debug-streaming.js:183 ======================================== -debug-streaming.js:186 🎉 No se encontraron errores graves -debug-streaming.js:199 -7️⃣ PRÓXIMOS PASOS RECOMENDADOS: -debug-streaming.js:213 🔧 4. Si persiste: plotManager.controlPlot("plot_16", "stop") y luego "start" -debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING -debug-streaming.js:9 ============================================================ -debug-streaming.js:23 -1️⃣ VERIFICANDO LIBRERÍAS... -debug-streaming.js:30 Chart.js: ✅ -debug-streaming.js:31 ChartStreaming: ✅ -debug-streaming.js:32 PlotManager: ✅ -debug-streaming.js:41 -2️⃣ VERIFICANDO PLOT MANAGER... -debug-streaming.js:44 ✅ PlotManager inicializado -debug-streaming.js:45 📊 Sesiones activas: 1 -debug-streaming.js:59 -3️⃣ ANALIZANDO SESIÓN DE PLOT... -debug-streaming.js:65 📈 Sesión encontrada: plot_16 -debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …} -debug-streaming.js:85 ✅ Escala realtime configurada correctamente -debug-streaming.js:93 ✅ Streaming habilitado en el chart -debug-streaming.js:98 -4️⃣ VERIFICANDO DATOS DEL BACKEND... - 📈 Plot debugging enabled. Check console for detailed logs. - ⚡ TEST RÁPIDO DE STREAMING -debug-streaming.js:279 ------------------------------ -debug-streaming.js:288 plotManager: ✅ true -debug-streaming.js:288 sessions: ✅ 1 -debug-streaming.js:288 chartStreaming: ✅ true -debug-streaming.js:293 -🧪 Probando sesión: plot_16 -debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING -debug-streaming.js:9 ============================================================ -debug-streaming.js:23 -1️⃣ VERIFICANDO LIBRERÍAS... -debug-streaming.js:30 Chart.js: ✅ -debug-streaming.js:31 ChartStreaming: ✅ -debug-streaming.js:32 PlotManager: ✅ -debug-streaming.js:41 -2️⃣ VERIFICANDO PLOT MANAGER... -debug-streaming.js:44 ✅ PlotManager inicializado -debug-streaming.js:45 📊 Sesiones activas: 1 -debug-streaming.js:59 -3️⃣ ANALIZANDO SESIÓN DE PLOT... -debug-streaming.js:65 📈 Sesión encontrada: plot_16 -debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …} -debug-streaming.js:85 ✅ Escala realtime configurada correctamente -debug-streaming.js:93 ✅ Streaming habilitado en el chart -debug-streaming.js:98 -4️⃣ VERIFICANDO DATOS DEL BACKEND... -plotting.js:20 📈 Plot plot_16: Received data: {data_points_count: 35, datasets: Array(1), is_active: true, is_paused: false, last_update: 1753115887.7399745, …} -plotting.js:20 📈 Plot plot_16: Processing 1 datasets for streaming -plotting.js:20 📈 Plot plot_16: Adding 17 new points for UR29_Brix -chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115884519.0737, y=44.65407943725586 -chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115884719.9353, y=46.29325866699219 -chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115884920.8208, y=47.510704040527344 -chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115885121.4167, y=47.90060806274414 -chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115885323.425, y=47.90060806274414 -chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115885522.0952, y=49.372684478759766 -chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115885724.864, y=50.64583206176758 -chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115885925.8164, y=50.68561935424805 -chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115886127.653, y=51.82349395751953 -chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115886327.9204, y=51.831451416015625 -chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115886529.75, y=52.46802520751953 -chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115886731.3887, y=52.810184478759766 -chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115886934.3843, y=53.60590362548828 -chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115887135.6711, y=53.60590362548828 -chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115887337.3464, y=54.2265625 -chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115887538.3455, y=54.2265625 -chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115887739.9746, y=54.65625 -plotting.js:20 📈 Plot plot_16: Updated last timestamp for UR29_Brix to 1753115887739.9746 -chartjs-plugin-streaming.js:75 📈 RealTimeScale DEBUG - scaleOptions: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …} -chartjs-plugin-streaming.js:76 📈 RealTimeScale DEBUG - me.options: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …} -chartjs-plugin-streaming.js:77 📈 RealTimeScale DEBUG - me.options.realtime: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …} -plotting.js:20 📈 Plot plot_16: Fetching data from backend... -chartjs-plugin-streaming.js:81 📈 RealTimeScale DEBUG - onRefresh resolved: null object -chartjs-plugin-streaming.js:93 📈 RealTimeScale initialized: {duration: 60000, refresh: 500, pause: false, hasOnRefresh: false} -debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING -debug-streaming.js:9 ============================================================ -debug-streaming.js:23 -1️⃣ VERIFICANDO LIBRERÍAS... -debug-streaming.js:30 Chart.js: ✅ -debug-streaming.js:31 ChartStreaming: ✅ -debug-streaming.js:32 PlotManager: ✅ -debug-streaming.js:41 -2️⃣ VERIFICANDO PLOT MANAGER... -debug-streaming.js:44 ✅ PlotManager inicializado -debug-streaming.js:45 📊 Sesiones activas: 1 -debug-streaming.js:59 -3️⃣ ANALIZANDO SESIÓN DE PLOT... -debug-streaming.js:65 📈 Sesión encontrada: plot_16 -debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …} -debug-streaming.js:85 ✅ Escala realtime configurada correctamente -debug-streaming.js:93 ✅ Streaming habilitado en el chart -debug-streaming.js:98 -4️⃣ VERIFICANDO DATOS DEL BACKEND... -plotting.js:9 📈 Plot debugging enabled. Check console for detailed logs. -debug-streaming.js:278 ⚡ TEST RÁPIDO DE STREAMING -debug-streaming.js:279 ------------------------------ -debug-streaming.js:288 plotManager: ✅ true -debug-streaming.js:288 sessions: ✅ 1 -debug-streaming.js:288 chartStreaming: ✅ true -debug-streaming.js:293 -🧪 Probando sesión: plot_16 -debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING -debug-streaming.js:9 ============================================================ -debug-streaming.js:23 -1️⃣ VERIFICANDO LIBRERÍAS... -debug-streaming.js:30 Chart.js: ✅ -debug-streaming.js:31 ChartStreaming: ✅ -debug-streaming.js:32 PlotManager: ✅ -debug-streaming.js:41 -2️⃣ VERIFICANDO PLOT MANAGER... -debug-streaming.js:44 ✅ PlotManager inicializado -debug-streaming.js:45 📊 Sesiones activas: 1 -debug-streaming.js:59 -3️⃣ ANALIZANDO SESIÓN DE PLOT... -debug-streaming.js:65 📈 Sesión encontrada: plot_16 -debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …} -debug-streaming.js:85 ✅ Escala realtime configurada correctamente -debug-streaming.js:93 ✅ Streaming habilitado en el chart -debug-streaming.js:98 -4️⃣ VERIFICANDO DATOS DEL BACKEND... -debug-streaming.js:103 📊 Respuesta del backend: {success: true, datasets: 1, totalPoints: 37, isActive: true, isPaused: false} -debug-streaming.js:112 ✅ Backend devuelve datos -debug-streaming.js:116 📈 Primer dataset: {label: 'UR29_Brix', dataPoints: 37, samplePoint: {…}} -debug-streaming.js:123 ✅ Dataset tiene puntos de datos -debug-streaming.js:130 ⏰ Análisis de timestamps: {firstPointTime: 1753115880879.299, currentTime: 1753115888236, differenceMs: 7356.700927734375, differenceSec: 7, isRecent: true} -debug-streaming.js:156 -5️⃣ PROBANDO FUNCIONALIDAD STREAMING... -chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115888237, y=23.50854350922291 -debug-streaming.js:165 🧪 Test de addStreamingData: ✅ -debug-streaming.js:170 📊 Puntos después del test: 57 -debug-streaming.js:182 -6️⃣ RESUMEN DEL DIAGNÓSTICO -debug-streaming.js:183 ======================================== -debug-streaming.js:186 🎉 No se encontraron errores graves -debug-streaming.js:199 -7️⃣ PRÓXIMOS PASOS RECOMENDADOS: -debug-streaming.js:213 🔧 4. Si persiste: plotManager.controlPlot("plot_16", "stop") y luego "start" -debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING -debug-streaming.js:9 ============================================================ -debug-streaming.js:23 -1️⃣ VERIFICANDO LIBRERÍAS... -debug-streaming.js:30 Chart.js: ✅ -debug-streaming.js:31 ChartStreaming: ✅ -debug-streaming.js:32 PlotManager: ✅ -debug-streaming.js:41 -2️⃣ VERIFICANDO PLOT MANAGER... -debug-streaming.js:44 ✅ PlotManager inicializado -debug-streaming.js:45 📊 Sesiones activas: 1 -debug-streaming.js:59 -3️⃣ ANALIZANDO SESIÓN DE PLOT... -debug-streaming.js:65 📈 Sesión encontrada: plot_16 -debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …} -debug-streaming.js:85 ✅ Escala realtime configurada correctamente -debug-streaming.js:93 ✅ Streaming habilitado en el chart -debug-streaming.js:98 -4️⃣ VERIFICANDO DATOS DEL BACKEND... -plotting.js:9 📈 Plot debugging enabled. Check console for detailed logs. -debug-streaming.js:278 ⚡ TEST RÁPIDO DE STREAMING -debug-streaming.js:279 ------------------------------ -debug-streaming.js:288 plotManager: ✅ true -debug-streaming.js:288 sessions: ✅ 1 -debug-streaming.js:288 chartStreaming: ✅ true -debug-streaming.js:293 -🧪 Probando sesión: plot_16 -debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING -debug-streaming.js:9 ============================================================ -debug-streaming.js:23 -1️⃣ VERIFICANDO LIBRERÍAS... -debug-streaming.js:30 Chart.js: ✅ -debug-streaming.js:31 ChartStreaming: ✅ -debug-streaming.js:32 PlotManager: ✅ -debug-streaming.js:41 -2️⃣ VERIFICANDO PLOT MANAGER... -debug-streaming.js:44 ✅ PlotManager inicializado -debug-streaming.js:45 📊 Sesiones activas: 1 -debug-streaming.js:59 -3️⃣ ANALIZANDO SESIÓN DE PLOT... -debug-streaming.js:65 📈 Sesión encontrada: plot_16 -debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …} -debug-streaming.js:85 ✅ Escala realtime configurada correctamente -debug-streaming.js:93 ✅ Streaming habilitado en el chart -debug-streaming.js:98 -4️⃣ VERIFICANDO DATOS DEL BACKEND... -debug-streaming.js:103 📊 Respuesta del backend: {success: true, datasets: 1, totalPoints: 38, isActive: true, isPaused: false} -debug-streaming.js:112 ✅ Backend devuelve datos -debug-streaming.js:116 📈 Primer dataset: {label: 'UR29_Brix', dataPoints: 38, samplePoint: {…}} -debug-streaming.js:123 ✅ Dataset tiene puntos de datos -debug-streaming.js:130 ⏰ Análisis de timestamps: {firstPointTime: 1753115880879.299, currentTime: 1753115888548, differenceMs: 7668.700927734375, differenceSec: 8, isRecent: true} -debug-streaming.js:156 -5️⃣ PROBANDO FUNCIONALIDAD STREAMING... -chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115888548, y=4.850139391135455 -debug-streaming.js:165 🧪 Test de addStreamingData: ✅ -debug-streaming.js:170 📊 Puntos después del test: 58 -debug-streaming.js:182 -6️⃣ RESUMEN DEL DIAGNÓSTICO -debug-streaming.js:183 ======================================== -debug-streaming.js:186 🎉 No se encontraron errores graves -debug-streaming.js:199 -7️⃣ PRÓXIMOS PASOS RECOMENDADOS: -debug-streaming.js:213 🔧 4. Si persiste: plotManager.controlPlot("plot_16", "stop") y luego "start" -chartjs-plugin-streaming.js:75 📈 RealTimeScale DEBUG - scaleOptions: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …} -chartjs-plugin-streaming.js:76 📈 RealTimeScale DEBUG - me.options: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …} -chartjs-plugin-streaming.js:77 📈 RealTimeScale DEBUG - me.options.realtime: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …} -chartjs-plugin-streaming.js:81 📈 RealTimeScale DEBUG - onRefresh resolved: null object -chartjs-plugin-streaming.js:93 📈 RealTimeScale initialized: {duration: 60000, refresh: 500, pause: false, hasOnRefresh: false} -debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING -debug-streaming.js:9 ============================================================ -debug-streaming.js:23 -1️⃣ VERIFICANDO LIBRERÍAS... -debug-streaming.js:30 Chart.js: ✅ -debug-streaming.js:31 ChartStreaming: ✅ -debug-streaming.js:32 PlotManager: ✅ -debug-streaming.js:41 -2️⃣ VERIFICANDO PLOT MANAGER... -debug-streaming.js:44 ✅ PlotManager inicializado -debug-streaming.js:45 📊 Sesiones activas: 1 -debug-streaming.js:59 -3️⃣ ANALIZANDO SESIÓN DE PLOT... -debug-streaming.js:65 📈 Sesión encontrada: plot_16 -debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …} -debug-streaming.js:85 ✅ Escala realtime configurada correctamente -debug-streaming.js:93 ✅ Streaming habilitado en el chart -debug-streaming.js:98 -4️⃣ VERIFICANDO DATOS DEL BACKEND... -plotting.js:9 📈 Plot debugging enabled. Check console for detailed logs. -debug-streaming.js:278 ⚡ TEST RÁPIDO DE STREAMING -debug-streaming.js:279 ------------------------------ -debug-streaming.js:288 plotManager: ✅ true -debug-streaming.js:288 sessions: ✅ 1 -debug-streaming.js:288 chartStreaming: ✅ true -debug-streaming.js:293 -🧪 Probando sesión: plot_16 -debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING -debug-streaming.js:9 ============================================================ -debug-streaming.js:23 -1️⃣ VERIFICANDO LIBRERÍAS... -debug-streaming.js:30 Chart.js: ✅ -debug-streaming.js:31 ChartStreaming: ✅ -debug-streaming.js:32 PlotManager: ✅ -debug-streaming.js:41 -2️⃣ VERIFICANDO PLOT MANAGER... -debug-streaming.js:44 ✅ PlotManager inicializado -debug-streaming.js:45 📊 Sesiones activas: 1 -debug-streaming.js:59 -3️⃣ ANALIZANDO SESIÓN DE PLOT... -debug-streaming.js:65 📈 Sesión encontrada: plot_16 -debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …} -debug-streaming.js:85 ✅ Escala realtime configurada correctamente -debug-streaming.js:93 ✅ Streaming habilitado en el chart -debug-streaming.js:98 -4️⃣ VERIFICANDO DATOS DEL BACKEND... -plotting.js:20 📈 Plot plot_16: Updating streaming chart with 1 datasets -plotting.js:20 - Variable 1: UR29_Brix (40 points) -plotting.js:20 📈 Plot plot_16: Added 0 new points to dataset 0 -debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING -debug-streaming.js:9 ============================================================ -debug-streaming.js:23 -1️⃣ VERIFICANDO LIBRERÍAS... -debug-streaming.js:30 Chart.js: ✅ -debug-streaming.js:31 ChartStreaming: ✅ -debug-streaming.js:32 PlotManager: ✅ -debug-streaming.js:41 -2️⃣ VERIFICANDO PLOT MANAGER... -debug-streaming.js:44 ✅ PlotManager inicializado -debug-streaming.js:45 📊 Sesiones activas: 1 -debug-streaming.js:59 -3️⃣ ANALIZANDO SESIÓN DE PLOT... -debug-streaming.js:65 📈 Sesión encontrada: plot_16 -debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …} -debug-streaming.js:85 ✅ Escala realtime configurada correctamente -debug-streaming.js:93 ✅ Streaming habilitado en el chart -debug-streaming.js:98 -4️⃣ VERIFICANDO DATOS DEL BACKEND... -plotting.js:9 📈 Plot debugging enabled. Check console for detailed logs. -debug-streaming.js:278 ⚡ TEST RÁPIDO DE STREAMING -debug-streaming.js:279 ------------------------------ -debug-streaming.js:288 plotManager: ✅ true -debug-streaming.js:288 sessions: ✅ 1 -debug-streaming.js:288 chartStreaming: ✅ true -debug-streaming.js:293 -🧪 Probando sesión: plot_16 -debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING -debug-streaming.js:9 ============================================================ -debug-streaming.js:23 -1️⃣ VERIFICANDO LIBRERÍAS... -debug-streaming.js:30 Chart.js: ✅ -debug-streaming.js:31 ChartStreaming: ✅ -debug-streaming.js:32 PlotManager: ✅ -debug-streaming.js:41 -2️⃣ VERIFICANDO PLOT MANAGER... -debug-streaming.js:44 ✅ PlotManager inicializado -debug-streaming.js:45 📊 Sesiones activas: 1 -debug-streaming.js:59 -3️⃣ ANALIZANDO SESIÓN DE PLOT... -debug-streaming.js:65 📈 Sesión encontrada: plot_16 -debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …} -debug-streaming.js:85 ✅ Escala realtime configurada correctamente -debug-streaming.js:93 ✅ Streaming habilitado en el chart -debug-streaming.js:98 -4️⃣ VERIFICANDO DATOS DEL BACKEND... -chartjs-plugin-streaming.js:75 📈 RealTimeScale DEBUG - scaleOptions: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …} -chartjs-plugin-streaming.js:76 📈 RealTimeScale DEBUG - me.options: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …} -chartjs-plugin-streaming.js:77 📈 RealTimeScale DEBUG - me.options.realtime: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …} -plotting.js:20 📈 Plot plot_16: Fetching data from backend... -chartjs-plugin-streaming.js:81 📈 RealTimeScale DEBUG - onRefresh resolved: null object -chartjs-plugin-streaming.js:93 📈 RealTimeScale initialized: {duration: 60000, refresh: 500, pause: false, hasOnRefresh: false} - 📊 Respuesta del backend: {success: true, datasets: 1, totalPoints: 42, isActive: true, isPaused: false} - ✅ Backend devuelve datos - 📈 Primer dataset: {label: 'UR29_Brix', dataPoints: 42, samplePoint: {…}} - ✅ Dataset tiene puntos de datos - ⏰ Análisis de timestamps: {firstPointTime: 1753115880879.299, currentTime: 1753115889176, differenceMs: 8296.700927734375, differenceSec: 8, isRecent: true} - -5️⃣ PROBANDO FUNCIONALIDAD STREAMING... - 📈 Added point to dataset 0 (UR29_Brix): x=1753115889176, y=42.83092459112214 - 🧪 Test de addStreamingData: ✅ - 📊 Puntos después del test: 59 - -6️⃣ RESUMEN DEL DIAGNÓSTICO -debug-streaming.js:183 ======================================== -debug-streaming.js:186 🎉 No se encontraron errores graves -debug-streaming.js:199 -7️⃣ PRÓXIMOS PASOS RECOMENDADOS: -debug-streaming.js:213 🔧 4. Si persiste: plotManager.controlPlot("plot_16", "stop") y luego "start" -debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING -debug-streaming.js:9 ============================================================ -debug-streaming.js:23 -1️⃣ VERIFICANDO LIBRERÍAS... -debug-streaming.js:30 Chart.js: ✅ -debug-streaming.js:31 ChartStreaming: ✅ -debug-streaming.js:32 PlotManager: ✅ -debug-streaming.js:41 -2️⃣ VERIFICANDO PLOT MANAGER... -debug-streaming.js:44 ✅ PlotManager inicializado -debug-streaming.js:45 📊 Sesiones activas: 1 -debug-streaming.js:59 -3️⃣ ANALIZANDO SESIÓN DE PLOT... -debug-streaming.js:65 📈 Sesión encontrada: plot_16 -debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …} -debug-streaming.js:85 ✅ Escala realtime configurada correctamente -debug-streaming.js:93 ✅ Streaming habilitado en el chart -debug-streaming.js:98 -4️⃣ VERIFICANDO DATOS DEL BACKEND... -plotting.js:9 📈 Plot debugging enabled. Check console for detailed logs. -debug-streaming.js:278 ⚡ TEST RÁPIDO DE STREAMING -debug-streaming.js:279 ------------------------------ -debug-streaming.js:288 plotManager: ✅ true -debug-streaming.js:288 sessions: ✅ 1 -debug-streaming.js:288 chartStreaming: ✅ true -debug-streaming.js:293 -🧪 Probando sesión: plot_16 -debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING -debug-streaming.js:9 ============================================================ -debug-streaming.js:23 -1️⃣ VERIFICANDO LIBRERÍAS... -debug-streaming.js:30 Chart.js: ✅ -debug-streaming.js:31 ChartStreaming: ✅ -debug-streaming.js:32 PlotManager: ✅ -debug-streaming.js:41 -2️⃣ VERIFICANDO PLOT MANAGER... -debug-streaming.js:44 ✅ PlotManager inicializado -debug-streaming.js:45 📊 Sesiones activas: 1 -debug-streaming.js:59 -3️⃣ ANALIZANDO SESIÓN DE PLOT... -debug-streaming.js:65 📈 Sesión encontrada: plot_16 -debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …} -debug-streaming.js:85 ✅ Escala realtime configurada correctamente -debug-streaming.js:93 ✅ Streaming habilitado en el chart -debug-streaming.js:98 -4️⃣ VERIFICANDO DATOS DEL BACKEND... -debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING -debug-streaming.js:9 ============================================================ -debug-streaming.js:23 -1️⃣ VERIFICANDO LIBRERÍAS... -debug-streaming.js:30 Chart.js: ✅ -debug-streaming.js:31 ChartStreaming: ✅ -debug-streaming.js:32 PlotManager: ✅ -debug-streaming.js:41 -2️⃣ VERIFICANDO PLOT MANAGER... -debug-streaming.js:44 ✅ PlotManager inicializado -debug-streaming.js:45 📊 Sesiones activas: 1 -debug-streaming.js:59 -3️⃣ ANALIZANDO SESIÓN DE PLOT... -debug-streaming.js:65 📈 Sesión encontrada: plot_16 -debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …} -debug-streaming.js:85 ✅ Escala realtime configurada correctamente -debug-streaming.js:93 ✅ Streaming habilitado en el chart -debug-streaming.js:98 -4️⃣ VERIFICANDO DATOS DEL BACKEND... -plotting.js:9 📈 Plot debugging enabled. Check console for detailed logs. -debug-streaming.js:278 ⚡ TEST RÁPIDO DE STREAMING -debug-streaming.js:279 ------------------------------ -debug-streaming.js:288 plotManager: ✅ true -debug-streaming.js:288 sessions: ✅ 1 -debug-streaming.js:288 chartStreaming: ✅ true -debug-streaming.js:293 -🧪 Probando sesión: plot_16 -debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING -debug-streaming.js:9 ============================================================ -debug-streaming.js:23 -1️⃣ VERIFICANDO LIBRERÍAS... -debug-streaming.js:30 Chart.js: ✅ -debug-streaming.js:31 ChartStreaming: ✅ -debug-streaming.js:32 PlotManager: ✅ -debug-streaming.js:41 -2️⃣ VERIFICANDO PLOT MANAGER... - ✅ PlotManager inicializado - 📊 Sesiones activas: 1 - -3️⃣ ANALIZANDO SESIÓN DE PLOT... - 📈 Sesión encontrada: plot_16 - Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …} - ✅ Escala realtime configurada correctamente - ✅ Streaming habilitado en el chart - -4️⃣ VERIFICANDO DATOS DEL BACKEND... - 📊 Respuesta del backend: {success: true, datasets: 1, totalPoints: 43, isActive: true, isPaused: false} - ✅ Backend devuelve datos - 📈 Primer dataset: {label: 'UR29_Brix', dataPoints: 43, samplePoint: {…}} - ✅ Dataset tiene puntos de datos - ⏰ Análisis de timestamps: {firstPointTime: 1753115880879.299, currentTime: 1753115889485, differenceMs: 8605.700927734375, differenceSec: 9, isRecent: true} - -5️⃣ PROBANDO FUNCIONALIDAD STREAMING... - 📈 Added point to dataset 0 (UR29_Brix): x=1753115889485, y=0.23291564278824506 - 🧪 Test de addStreamingData: ✅ - 📊 Puntos después del test: 60 - -6️⃣ RESUMEN DEL DIAGNÓSTICO - ======================================== - 🎉 No se encontraron errores graves - -7️⃣ PRÓXIMOS PASOS RECOMENDADOS: - 🔧 4. Si persiste: plotManager.controlPlot("plot_16", "stop") y luego "start" - 📈 Plot plot_16: Streaming resumed - 📈 RealTimeScale DEBUG - scaleOptions: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …} - 📈 RealTimeScale DEBUG - me.options: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …} - 📈 RealTimeScale DEBUG - me.options.realtime: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …} - 📈 RealTimeScale DEBUG - onRefresh resolved: null object - 📈 RealTimeScale initialized: {duration: 60000, refresh: 500, pause: false, hasOnRefresh: false} - 🔧 DIAGNÓSTICO DETALLADO DE STREAMING - ============================================================ - -1️⃣ VERIFICANDO LIBRERÍAS... - Chart.js: ✅ - ChartStreaming: ✅ - PlotManager: ✅ - -2️⃣ VERIFICANDO PLOT MANAGER... - ✅ PlotManager inicializado - 📊 Sesiones activas: 1 - -3️⃣ ANALIZANDO SESIÓN DE PLOT... - 📈 Sesión encontrada: plot_16 - Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …} - ✅ Escala realtime configurada correctamente - ✅ Streaming habilitado en el chart - -4️⃣ VERIFICANDO DATOS DEL BACKEND... - 📈 Plot debugging enabled. Check console for detailed logs. - ⚡ TEST RÁPIDO DE STREAMING - ------------------------------ - plotManager: ✅ true - sessions: ✅ 1 - chartStreaming: ✅ true - -🧪 Probando sesión: plot_16 - 🔧 DIAGNÓSTICO DETALLADO DE STREAMING - ============================================================ - -1️⃣ VERIFICANDO LIBRERÍAS... - Chart.js: ✅ - ChartStreaming: ✅ - PlotManager: ✅ - -2️⃣ VERIFICANDO PLOT MANAGER... - ✅ PlotManager inicializado - 📊 Sesiones activas: 1 - -3️⃣ ANALIZANDO SESIÓN DE PLOT... - 📈 Sesión encontrada: plot_16 - Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …} - ✅ Escala realtime configurada correctamente - ✅ Streaming habilitado en el chart - -4️⃣ VERIFICANDO DATOS DEL BACKEND... - 📈 Plot plot_16: Updating streaming chart with 1 datasets - - Variable 1: UR29_Brix (45 points) - 📈 Plot plot_16: Added 0 new points to dataset 0 - 🔧 DIAGNÓSTICO DETALLADO DE STREAMING - ============================================================ - -1️⃣ VERIFICANDO LIBRERÍAS... - Chart.js: ✅ - ChartStreaming: ✅ - PlotManager: ✅ - -2️⃣ VERIFICANDO PLOT MANAGER... - ✅ PlotManager inicializado - 📊 Sesiones activas: 1 - -3️⃣ ANALIZANDO SESIÓN DE PLOT... - 📈 Sesión encontrada: plot_16 - Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …} - ✅ Escala realtime configurada correctamente - ✅ Streaming habilitado en el chart - -4️⃣ VERIFICANDO DATOS DEL BACKEND... - 📈 Plot debugging enabled. Check console for detailed logs. - ⚡ TEST RÁPIDO DE STREAMING - ------------------------------ - plotManager: ✅ true - sessions: ✅ 1 - chartStreaming: ✅ true - -🧪 Probando sesión: plot_16 - 🔧 DIAGNÓSTICO DETALLADO DE STREAMING - ============================================================ - -1️⃣ VERIFICANDO LIBRERÍAS... - Chart.js: ✅ - ChartStreaming: ✅ - PlotManager: ✅ - -2️⃣ VERIFICANDO PLOT MANAGER... - ✅ PlotManager inicializado - 📊 Sesiones activas: 1 - -3️⃣ ANALIZANDO SESIÓN DE PLOT... - 📈 Sesión encontrada: plot_16 - Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …} - ✅ Escala realtime configurada correctamente - ✅ Streaming habilitado en el chart - -4️⃣ VERIFICANDO DATOS DEL BACKEND... - 📈 RealTimeScale DEBUG - scaleOptions: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …} - 📈 RealTimeScale DEBUG - me.options: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …} - 📈 RealTimeScale DEBUG - me.options.realtime: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …} - 📈 Plot plot_16: Fetching data from backend... - 📈 RealTimeScale DEBUG - onRefresh resolved: null object - 📈 RealTimeScale initialized: {duration: 60000, refresh: 500, pause: false, hasOnRefresh: false} - 📊 Respuesta del backend: {success: true, datasets: 1, totalPoints: 46, isActive: true, isPaused: false} - ✅ Backend devuelve datos - 📈 Primer dataset: {label: 'UR29_Brix', dataPoints: 46, samplePoint: {…}} - ✅ Dataset tiene puntos de datos - ⏰ Análisis de timestamps: {firstPointTime: 1753115880879.299, currentTime: 1753115890109, differenceMs: 9229.700927734375, differenceSec: 9, isRecent: true} - -5️⃣ PROBANDO FUNCIONALIDAD STREAMING... - 📈 Added point to dataset 0 (UR29_Brix): x=1753115890109, y=15.728858368162268 - 🧪 Test de addStreamingData: ✅ - 📊 Puntos después del test: 61 - -6️⃣ RESUMEN DEL DIAGNÓSTICO - ======================================== - 🎉 No se encontraron errores graves - -7️⃣ PRÓXIMOS PASOS RECOMENDADOS: - 🔧 4. Si persiste: plotManager.controlPlot("plot_16", "stop") y luego "start" - 🔧 DIAGNÓSTICO DETALLADO DE STREAMING - ============================================================ - -1️⃣ VERIFICANDO LIBRERÍAS... - Chart.js: ✅ - ChartStreaming: ✅ - PlotManager: ✅ - -2️⃣ VERIFICANDO PLOT MANAGER... - ✅ PlotManager inicializado - 📊 Sesiones activas: 1 - -3️⃣ ANALIZANDO SESIÓN DE PLOT... - 📈 Sesión encontrada: plot_16 - Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …} - ✅ Escala realtime configurada correctamente - ✅ Streaming habilitado en el chart - -4️⃣ VERIFICANDO DATOS DEL BACKEND... - 📈 Plot debugging enabled. Check console for detailed logs. - ⚡ TEST RÁPIDO DE STREAMING - ------------------------------ - plotManager: ✅ true - sessions: ✅ 1 - chartStreaming: ✅ true - -🧪 Probando sesión: plot_16 - 🔧 DIAGNÓSTICO DETALLADO DE STREAMING - ============================================================ - -1️⃣ VERIFICANDO LIBRERÍAS... - Chart.js: ✅ - ChartStreaming: ✅ - PlotManager: ✅ - -2️⃣ VERIFICANDO PLOT MANAGER... - ✅ PlotManager inicializado - 📊 Sesiones activas: 1 - -3️⃣ ANALIZANDO SESIÓN DE PLOT... - 📈 Sesión encontrada: plot_16 - Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …} - ✅ Escala realtime configurada correctamente - ✅ Streaming habilitado en el chart - -4️⃣ VERIFICANDO DATOS DEL BACKEND... - 📊 Respuesta del backend: {success: true, datasets: 1, totalPoints: 48, isActive: true, isPaused: false} - ✅ Backend devuelve datos - 📈 Primer dataset: {label: 'UR29_Brix', dataPoints: 48, samplePoint: {…}} - ✅ Dataset tiene puntos de datos - ⏰ Análisis de timestamps: {firstPointTime: 1753115880879.299, currentTime: 1753115890413, differenceMs: 9533.700927734375, differenceSec: 10, isRecent: true} - -5️⃣ PROBANDO FUNCIONALIDAD STREAMING... - 📈 Added point to dataset 0 (UR29_Brix): x=1753115890413, y=93.73804066063487 - 🧪 Test de addStreamingData: ✅ - 📊 Puntos después del test: 62 - -6️⃣ RESUMEN DEL DIAGNÓSTICO - ======================================== - 🎉 No se encontraron errores graves - -7️⃣ PRÓXIMOS PASOS RECOMENDADOS: - 🔧 4. Si persiste: plotManager.controlPlot("plot_16", "stop") y luego "start" - 🔧 DIAGNÓSTICO DETALLADO DE STREAMING - ============================================================ - -1️⃣ VERIFICANDO LIBRERÍAS... - Chart.js: ✅ - ChartStreaming: ✅ - PlotManager: ✅ - -2️⃣ VERIFICANDO PLOT MANAGER... - ✅ PlotManager inicializado - 📊 Sesiones activas: 1 - -3️⃣ ANALIZANDO SESIÓN DE PLOT... - 📈 Sesión encontrada: plot_16 - Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …} - ✅ Escala realtime configurada correctamente - ✅ Streaming habilitado en el chart - -4️⃣ VERIFICANDO DATOS DEL BACKEND... - 📈 Plot debugging enabled. Check console for detailed logs. - ⚡ TEST RÁPIDO DE STREAMING - ------------------------------ - plotManager: ✅ true - sessions: ✅ 1 - chartStreaming: ✅ true - -🧪 Probando sesión: plot_16 - 🔧 DIAGNÓSTICO DETALLADO DE STREAMING - ============================================================ - -1️⃣ VERIFICANDO LIBRERÍAS... - Chart.js: ✅ - ChartStreaming: ✅ - PlotManager: ✅ - -2️⃣ VERIFICANDO PLOT MANAGER... - ✅ PlotManager inicializado - 📊 Sesiones activas: 1 - -3️⃣ ANALIZANDO SESIÓN DE PLOT... - 📈 Sesión encontrada: plot_16 - Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …} - ✅ Escala realtime configurada correctamente - ✅ Streaming habilitado en el chart - -4️⃣ VERIFICANDO DATOS DEL BACKEND... - 📈 RealTimeScale DEBUG - scaleOptions: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …} - 📈 RealTimeScale DEBUG - me.options: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …} - 📈 RealTimeScale DEBUG - me.options.realtime: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …} - 📈 RealTimeScale DEBUG - onRefresh resolved: null object - 📈 RealTimeScale initialized: {duration: 60000, refresh: 500, pause: false, hasOnRefresh: false}