Flowfile 0.4.1__py3-none-any.whl → 0.5.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- build_backends/main.py +25 -22
- build_backends/main_prd.py +10 -19
- flowfile/__init__.py +179 -73
- flowfile/__main__.py +10 -7
- flowfile/api.py +52 -59
- flowfile/web/__init__.py +14 -9
- flowfile/web/static/assets/AdminView-49392a9a.js +713 -0
- flowfile/web/static/assets/AdminView-f53bad23.css +129 -0
- flowfile/web/static/assets/CloudConnectionView-36bcd6df.css +72 -0
- flowfile/web/static/assets/{CloudConnectionManager-d3248f8d.js → CloudConnectionView-f13f202b.js} +11 -11
- flowfile/web/static/assets/{CloudStorageReader-d65bf041.js → CloudStorageReader-0023d4a5.js} +10 -8
- flowfile/web/static/assets/{CloudStorageReader-29d14fcc.css → CloudStorageReader-24c54524.css} +27 -27
- flowfile/web/static/assets/{CloudStorageWriter-b0ee067f.css → CloudStorageWriter-60547855.css} +26 -26
- flowfile/web/static/assets/{CloudStorageWriter-e83be3ed.js → CloudStorageWriter-8e781e11.js} +10 -8
- flowfile/web/static/assets/{ColumnSelector-47996a16.css → ColumnSelector-371637fb.css} +2 -2
- flowfile/web/static/assets/{ColumnSelector-cce661cf.js → ColumnSelector-8ad68ea9.js} +3 -5
- flowfile/web/static/assets/{ContextMenu-c13f91d0.css → ContextMenu-26d4dd27.css} +6 -6
- flowfile/web/static/assets/{ContextMenu-11a4652a.js → ContextMenu-31ee57f0.js} +3 -3
- flowfile/web/static/assets/{ContextMenu-160afb08.js → ContextMenu-69a74055.js} +3 -3
- flowfile/web/static/assets/{ContextMenu-cf18d2cc.js → ContextMenu-8e2051c6.js} +3 -3
- flowfile/web/static/assets/{ContextMenu-4c74eef1.css → ContextMenu-8ec1729e.css} +6 -6
- flowfile/web/static/assets/{ContextMenu-63cfa99b.css → ContextMenu-9b310c60.css} +6 -6
- flowfile/web/static/assets/{CrossJoin-d395d38c.js → CrossJoin-03df6938.js} +12 -10
- flowfile/web/static/assets/{CrossJoin-1119d18e.css → CrossJoin-71b4cc10.css} +20 -20
- flowfile/web/static/assets/CustomNode-59e99a86.css +32 -0
- flowfile/web/static/assets/{CustomNode-b812dc0b.js → CustomNode-8479239b.js} +36 -24
- flowfile/web/static/assets/{DatabaseConnectionSettings-7000bf2c.js → DatabaseConnectionSettings-869e3efd.js} +5 -4
- flowfile/web/static/assets/{DatabaseConnectionSettings-0c04b2e5.css → DatabaseConnectionSettings-e91df89a.css} +13 -13
- flowfile/web/static/assets/{DatabaseReader-ae61773c.css → DatabaseReader-36898a00.css} +24 -24
- flowfile/web/static/assets/{DatabaseReader-4f035d0c.js → DatabaseReader-c58b9552.js} +25 -15
- flowfile/web/static/assets/DatabaseView-6655afd6.css +57 -0
- flowfile/web/static/assets/{DatabaseManager-9662ec5b.js → DatabaseView-d26a9140.js} +11 -11
- flowfile/web/static/assets/{DatabaseWriter-2f570e53.css → DatabaseWriter-217a99f1.css} +19 -19
- flowfile/web/static/assets/{DatabaseWriter-f65dcd54.js → DatabaseWriter-4d05ddc7.js} +17 -10
- flowfile/web/static/assets/{designer-e3c150ec.css → DesignerView-a6d0ee84.css} +629 -538
- flowfile/web/static/assets/{designer-f3656d8c.js → DesignerView-e6f5c0e8.js} +1214 -3209
- flowfile/web/static/assets/{documentation-52b241e7.js → DocumentationView-2e78ef1b.js} +5 -5
- flowfile/web/static/assets/{documentation-12216a74.css → DocumentationView-fd46c656.css} +7 -7
- flowfile/web/static/assets/{ExploreData-2d0cf4db.css → ExploreData-10c5acc8.css} +13 -12
- flowfile/web/static/assets/{ExploreData-94c43dfc.js → ExploreData-7b54caca.js} +18 -9
- flowfile/web/static/assets/{ExternalSource-ac04b3cc.js → ExternalSource-3fa399b2.js} +9 -7
- flowfile/web/static/assets/{ExternalSource-e37b6275.css → ExternalSource-47ab05a3.css} +17 -17
- flowfile/web/static/assets/Filter-7494ea97.css +48 -0
- flowfile/web/static/assets/Filter-8cbbdbf3.js +287 -0
- flowfile/web/static/assets/{Formula-bb96803d.css → Formula-53d58c43.css} +7 -7
- flowfile/web/static/assets/{Formula-71472193.js → Formula-aac42b1e.js} +13 -11
- flowfile/web/static/assets/{FuzzyMatch-1010f966.css → FuzzyMatch-ad6361d6.css} +68 -69
- flowfile/web/static/assets/{FuzzyMatch-b317f631.js → FuzzyMatch-cd9bbfca.js} +12 -10
- flowfile/web/static/assets/{Pivot-cf333e3d.css → GraphSolver-c24dec17.css} +5 -5
- flowfile/web/static/assets/{GraphSolver-754a234f.js → GraphSolver-c7e6780e.js} +13 -11
- flowfile/web/static/assets/{GroupBy-6c6f9802.js → GroupBy-93c5d22b.js} +9 -7
- flowfile/web/static/assets/{GroupBy-b9505323.css → GroupBy-be7ac0bf.css} +10 -10
- flowfile/web/static/assets/{Join-fd79b451.css → Join-28b5e18f.css} +22 -22
- flowfile/web/static/assets/{Join-a1b800be.js → Join-a19b2de2.js} +13 -11
- flowfile/web/static/assets/LoginView-0df4ed0a.js +134 -0
- flowfile/web/static/assets/LoginView-d325d632.css +172 -0
- flowfile/web/static/assets/ManualInput-3702e677.css +293 -0
- flowfile/web/static/assets/{ManualInput-a9640276.js → ManualInput-8d3374b2.js} +170 -116
- flowfile/web/static/assets/{MultiSelect-97213888.js → MultiSelect-ad1b6243.js} +2 -2
- flowfile/web/static/assets/{MultiSelect.vue_vue_type_script_setup_true_lang-6ffe088a.js → MultiSelect.vue_vue_type_script_setup_true_lang-e278950d.js} +1 -1
- flowfile/web/static/assets/NodeDesigner-40b647c9.js +2610 -0
- flowfile/web/static/assets/NodeDesigner-5f53be3f.css +1429 -0
- flowfile/web/static/assets/{NumericInput-e638088a.js → NumericInput-7100234c.js} +2 -2
- flowfile/web/static/assets/{NumericInput.vue_vue_type_script_setup_true_lang-90eb2cba.js → NumericInput.vue_vue_type_script_setup_true_lang-5130219f.js} +5 -2
- flowfile/web/static/assets/{Output-ddc9079f.css → Output-35e97000.css} +6 -6
- flowfile/web/static/assets/{Output-76750610.js → Output-f5efd2aa.js} +60 -38
- flowfile/web/static/assets/{GraphSolver-f0cb7bfb.css → Pivot-0eda81b4.css} +5 -5
- flowfile/web/static/assets/{Pivot-7814803f.js → Pivot-d981d23c.js} +11 -9
- flowfile/web/static/assets/PivotValidation-0e905b1a.css +13 -0
- flowfile/web/static/assets/{PivotValidation-f92137d2.js → PivotValidation-39386e95.js} +3 -3
- flowfile/web/static/assets/PivotValidation-41b57ad6.css +13 -0
- flowfile/web/static/assets/{PivotValidation-76dd431a.js → PivotValidation-63de1f73.js} +3 -3
- flowfile/web/static/assets/{PolarsCode-650322d1.css → PolarsCode-2b1f1f23.css} +4 -4
- flowfile/web/static/assets/{PolarsCode-889c3008.js → PolarsCode-f9d69217.js} +18 -9
- flowfile/web/static/assets/PopOver-b22f049e.js +939 -0
- flowfile/web/static/assets/PopOver-d96599db.css +33 -0
- flowfile/web/static/assets/{Read-6b17491f.css → Read-36e7bd51.css} +12 -12
- flowfile/web/static/assets/{Read-637b72a7.js → Read-aec2e377.js} +83 -105
- flowfile/web/static/assets/{RecordCount-2b050c41.js → RecordCount-78ed6845.js} +6 -4
- flowfile/web/static/assets/{RecordId-81df7784.js → RecordId-2156e890.js} +8 -6
- flowfile/web/static/assets/{SQLQueryComponent-36cef432.css → SQLQueryComponent-1c2f26b4.css} +5 -5
- flowfile/web/static/assets/{SQLQueryComponent-88dcfe53.js → SQLQueryComponent-48c72f5b.js} +3 -3
- flowfile/web/static/assets/{Sample-258ad2a9.js → Sample-1352ca74.js} +6 -4
- flowfile/web/static/assets/SecretSelector-22b5ff89.js +113 -0
- flowfile/web/static/assets/SecretSelector-6329f743.css +43 -0
- flowfile/web/static/assets/{SecretManager-2a2cb7e2.js → SecretsView-17df66ee.js} +35 -36
- flowfile/web/static/assets/SecretsView-aa291340.css +38 -0
- flowfile/web/static/assets/{Select-850215fd.js → Select-0aee4c54.js} +9 -7
- flowfile/web/static/assets/{SettingsSection-55bae608.js → SettingsSection-0784e157.js} +3 -3
- flowfile/web/static/assets/{SettingsSection-71e6b7e3.css → SettingsSection-07fbbc39.css} +4 -4
- flowfile/web/static/assets/{SettingsSection-5c696bee.css → SettingsSection-26fe48d4.css} +4 -4
- flowfile/web/static/assets/{SettingsSection-2e4d03c4.css → SettingsSection-8f980839.css} +4 -4
- flowfile/web/static/assets/{SettingsSection-0e8d9123.js → SettingsSection-cd341bb6.js} +3 -3
- flowfile/web/static/assets/{SettingsSection-29b4fa6b.js → SettingsSection-f2002a6d.js} +3 -3
- flowfile/web/static/assets/{SingleSelect-bebd408b.js → SingleSelect-460cc0ea.js} +2 -2
- flowfile/web/static/assets/{SingleSelect.vue_vue_type_script_setup_true_lang-6093741c.js → SingleSelect.vue_vue_type_script_setup_true_lang-30741bb2.js} +1 -1
- flowfile/web/static/assets/{SliderInput-6a05ab61.js → SliderInput-5d926864.js} +7 -4
- flowfile/web/static/assets/SliderInput-f2e4f23c.css +4 -0
- flowfile/web/static/assets/{Sort-10ab48ed.js → Sort-3cdc971b.js} +9 -7
- flowfile/web/static/assets/{Unique-f9fb0809.css → Sort-8a871341.css} +10 -10
- flowfile/web/static/assets/{TextInput-df9d6259.js → TextInput-a2d0bfbd.js} +2 -2
- flowfile/web/static/assets/{TextInput.vue_vue_type_script_setup_true_lang-000e1178.js → TextInput.vue_vue_type_script_setup_true_lang-abad1ca2.js} +5 -2
- flowfile/web/static/assets/{TextToRows-5d2c1190.css → TextToRows-12afb4f4.css} +10 -10
- flowfile/web/static/assets/{TextToRows-6c2d93d8.js → TextToRows-918945f7.js} +11 -10
- flowfile/web/static/assets/{ToggleSwitch-0ff7ac52.js → ToggleSwitch-f0ef5196.js} +2 -2
- flowfile/web/static/assets/{ToggleSwitch.vue_vue_type_script_setup_true_lang-c6dc3029.js → ToggleSwitch.vue_vue_type_script_setup_true_lang-5605c793.js} +1 -1
- flowfile/web/static/assets/{UnavailableFields-5edd5322.css → UnavailableFields-54d2f518.css} +6 -6
- flowfile/web/static/assets/{UnavailableFields-1bab97cb.js → UnavailableFields-bdad6144.js} +4 -4
- flowfile/web/static/assets/{Union-af6c3d9b.css → Union-d6a8d7d5.css} +7 -7
- flowfile/web/static/assets/{Union-b563478a.js → Union-e8ab8c86.js} +8 -6
- flowfile/web/static/assets/{Unique-f90db5db.js → Unique-8cd4f976.js} +13 -22
- flowfile/web/static/assets/{Sort-3643d625.css → Unique-9fb2f567.css} +10 -10
- flowfile/web/static/assets/{Unpivot-1e422df3.css → Unpivot-710a2948.css} +7 -7
- flowfile/web/static/assets/{Unpivot-bcb0025f.js → Unpivot-8da14095.js} +10 -8
- flowfile/web/static/assets/{UnpivotValidation-c4e73b04.js → UnpivotValidation-6f7d89ff.js} +3 -3
- flowfile/web/static/assets/UnpivotValidation-d5ca3b7b.css +13 -0
- flowfile/web/static/assets/{VueGraphicWalker-bb8535e2.js → VueGraphicWalker-3fb312e1.js} +4 -4
- flowfile/web/static/assets/{VueGraphicWalker-ed5ab88b.css → VueGraphicWalker-430f0b86.css} +1 -1
- flowfile/web/static/assets/{api-4c8e3822.js → api-24483f0d.js} +1 -1
- flowfile/web/static/assets/{api-2d6adc4f.js → api-8b81fa73.js} +1 -1
- flowfile/web/static/assets/{dropDown-35135ba8.css → dropDown-3d8dc5fa.css} +40 -40
- flowfile/web/static/assets/{dropDown-1bca8a74.js → dropDown-ac0fda9d.js} +3 -3
- flowfile/web/static/assets/{fullEditor-2985687e.js → fullEditor-5497a84a.js} +11 -10
- flowfile/web/static/assets/{fullEditor-178376bb.css → fullEditor-a0be62b3.css} +74 -62
- flowfile/web/static/assets/{genericNodeSettings-924759c7.css → genericNodeSettings-3b2507ea.css} +10 -10
- flowfile/web/static/assets/{genericNodeSettings-0476ba4e.js → genericNodeSettings-99014e1d.js} +5 -5
- flowfile/web/static/assets/index-07dda503.js +38 -0
- flowfile/web/static/assets/index-3ba44389.js +2696 -0
- flowfile/web/static/assets/{index-50508d4d.css → index-e6289dd0.css} +1945 -569
- flowfile/web/static/assets/{index-246f201c.js → index-fb6493ae.js} +41626 -40869
- flowfile/web/static/assets/node.types-2c15bb7e.js +82 -0
- flowfile/web/static/assets/nodeInput-0eb13f1a.js +2 -0
- flowfile/web/static/assets/{outputCsv-d686eeaf.js → outputCsv-8f8ba42d.js} +3 -3
- flowfile/web/static/assets/outputCsv-b9a072af.css +2499 -0
- flowfile/web/static/assets/{outputExcel-8809ea2f.js → outputExcel-393f4fef.js} +3 -3
- flowfile/web/static/assets/{outputExcel-b41305c0.css → outputExcel-f5d272b2.css} +26 -26
- flowfile/web/static/assets/{outputParquet-53ba645a.js → outputParquet-07c81f65.js} +4 -4
- flowfile/web/static/assets/outputParquet-54597c3c.css +4 -0
- flowfile/web/static/assets/{readCsv-053bf97b.js → readCsv-07f6d9ad.js} +21 -20
- flowfile/web/static/assets/{readCsv-bca3ed53.css → readCsv-3bfac4c3.css} +15 -15
- flowfile/web/static/assets/{readExcel-e1b381ea.css → readExcel-3db6b763.css} +13 -13
- flowfile/web/static/assets/{readExcel-ad531eab.js → readExcel-ed69bc8f.js} +10 -12
- flowfile/web/static/assets/{readParquet-cee068e2.css → readParquet-c5244ad5.css} +4 -4
- flowfile/web/static/assets/{readParquet-58e899a1.js → readParquet-e3ed4528.js} +4 -7
- flowfile/web/static/assets/secrets.api-002e7d7e.js +65 -0
- flowfile/web/static/assets/{selectDynamic-b38de2ba.js → selectDynamic-80b92899.js} +5 -5
- flowfile/web/static/assets/{selectDynamic-aa913ff4.css → selectDynamic-f2fb394f.css} +21 -20
- flowfile/web/static/assets/{vue-codemirror.esm-db9b8936.js → vue-codemirror.esm-0965f39f.js} +31 -637
- flowfile/web/static/assets/{vue-content-loader.es-b5f3ac30.js → vue-content-loader.es-c506ad97.js} +1 -1
- flowfile/web/static/index.html +2 -2
- {flowfile-0.4.1.dist-info → flowfile-0.5.3.dist-info}/METADATA +4 -4
- flowfile-0.5.3.dist-info/RECORD +402 -0
- {flowfile-0.4.1.dist-info → flowfile-0.5.3.dist-info}/WHEEL +1 -1
- {flowfile-0.4.1.dist-info → flowfile-0.5.3.dist-info}/entry_points.txt +1 -0
- flowfile_core/__init__.py +13 -3
- flowfile_core/auth/jwt.py +51 -16
- flowfile_core/auth/models.py +32 -7
- flowfile_core/auth/password.py +89 -0
- flowfile_core/auth/secrets.py +8 -6
- flowfile_core/configs/__init__.py +9 -7
- flowfile_core/configs/flow_logger.py +15 -14
- flowfile_core/configs/node_store/__init__.py +72 -4
- flowfile_core/configs/node_store/nodes.py +155 -172
- flowfile_core/configs/node_store/user_defined_node_registry.py +108 -27
- flowfile_core/configs/settings.py +28 -15
- flowfile_core/database/connection.py +7 -6
- flowfile_core/database/init_db.py +96 -2
- flowfile_core/database/models.py +3 -1
- flowfile_core/fileExplorer/__init__.py +17 -0
- flowfile_core/fileExplorer/funcs.py +123 -57
- flowfile_core/fileExplorer/utils.py +10 -11
- flowfile_core/flowfile/_extensions/real_time_interface.py +10 -8
- flowfile_core/flowfile/analytics/analytics_processor.py +27 -24
- flowfile_core/flowfile/analytics/graphic_walker.py +11 -12
- flowfile_core/flowfile/analytics/utils.py +1 -1
- flowfile_core/flowfile/code_generator/code_generator.py +391 -279
- flowfile_core/flowfile/connection_manager/_connection_manager.py +6 -5
- flowfile_core/flowfile/connection_manager/models.py +1 -1
- flowfile_core/flowfile/database_connection_manager/db_connections.py +60 -44
- flowfile_core/flowfile/database_connection_manager/models.py +1 -1
- flowfile_core/flowfile/extensions.py +17 -12
- flowfile_core/flowfile/flow_data_engine/cloud_storage_reader.py +34 -32
- flowfile_core/flowfile/flow_data_engine/create/funcs.py +152 -103
- flowfile_core/flowfile/flow_data_engine/flow_data_engine.py +526 -477
- flowfile_core/flowfile/flow_data_engine/flow_file_column/interface.py +2 -2
- flowfile_core/flowfile/flow_data_engine/flow_file_column/main.py +92 -52
- flowfile_core/flowfile/flow_data_engine/flow_file_column/polars_type.py +12 -11
- flowfile_core/flowfile/flow_data_engine/flow_file_column/type_registry.py +6 -6
- flowfile_core/flowfile/flow_data_engine/flow_file_column/utils.py +26 -30
- flowfile_core/flowfile/flow_data_engine/fuzzy_matching/prepare_for_fuzzy_match.py +43 -32
- flowfile_core/flowfile/flow_data_engine/join/__init__.py +1 -1
- flowfile_core/flowfile/flow_data_engine/join/utils.py +11 -9
- flowfile_core/flowfile/flow_data_engine/join/verify_integrity.py +15 -11
- flowfile_core/flowfile/flow_data_engine/pivot_table.py +5 -7
- flowfile_core/flowfile/flow_data_engine/polars_code_parser.py +95 -82
- flowfile_core/flowfile/flow_data_engine/read_excel_tables.py +66 -65
- flowfile_core/flowfile/flow_data_engine/sample_data.py +27 -21
- flowfile_core/flowfile/flow_data_engine/subprocess_operations/__init__.py +1 -1
- flowfile_core/flowfile/flow_data_engine/subprocess_operations/models.py +13 -11
- flowfile_core/flowfile/flow_data_engine/subprocess_operations/subprocess_operations.py +360 -191
- flowfile_core/flowfile/flow_data_engine/threaded_processes.py +8 -8
- flowfile_core/flowfile/flow_data_engine/utils.py +101 -67
- flowfile_core/flowfile/flow_graph.py +1011 -561
- flowfile_core/flowfile/flow_graph_utils.py +31 -49
- flowfile_core/flowfile/flow_node/flow_node.py +332 -232
- flowfile_core/flowfile/flow_node/models.py +54 -41
- flowfile_core/flowfile/flow_node/schema_callback.py +14 -19
- flowfile_core/flowfile/graph_tree/graph_tree.py +41 -41
- flowfile_core/flowfile/handler.py +82 -32
- flowfile_core/flowfile/manage/compatibility_enhancements.py +493 -47
- flowfile_core/flowfile/manage/io_flowfile.py +391 -0
- flowfile_core/flowfile/node_designer/__init__.py +15 -13
- flowfile_core/flowfile/node_designer/_type_registry.py +34 -37
- flowfile_core/flowfile/node_designer/custom_node.py +162 -36
- flowfile_core/flowfile/node_designer/ui_components.py +136 -35
- flowfile_core/flowfile/schema_callbacks.py +77 -54
- flowfile_core/flowfile/setting_generator/__init__.py +0 -1
- flowfile_core/flowfile/setting_generator/setting_generator.py +6 -5
- flowfile_core/flowfile/setting_generator/settings.py +72 -55
- flowfile_core/flowfile/sources/external_sources/base_class.py +12 -10
- flowfile_core/flowfile/sources/external_sources/custom_external_sources/external_source.py +27 -17
- flowfile_core/flowfile/sources/external_sources/custom_external_sources/sample_users.py +9 -9
- flowfile_core/flowfile/sources/external_sources/factory.py +0 -1
- flowfile_core/flowfile/sources/external_sources/sql_source/models.py +45 -31
- flowfile_core/flowfile/sources/external_sources/sql_source/sql_source.py +198 -73
- flowfile_core/flowfile/sources/external_sources/sql_source/utils.py +250 -196
- flowfile_core/flowfile/util/calculate_layout.py +9 -13
- flowfile_core/flowfile/util/execution_orderer.py +25 -17
- flowfile_core/flowfile/util/node_skipper.py +4 -4
- flowfile_core/flowfile/utils.py +19 -21
- flowfile_core/main.py +26 -19
- flowfile_core/routes/auth.py +284 -11
- flowfile_core/routes/cloud_connections.py +25 -25
- flowfile_core/routes/logs.py +21 -29
- flowfile_core/routes/public.py +3 -3
- flowfile_core/routes/routes.py +77 -43
- flowfile_core/routes/secrets.py +25 -27
- flowfile_core/routes/user_defined_components.py +483 -4
- flowfile_core/run_lock.py +0 -1
- flowfile_core/schemas/__init__.py +4 -6
- flowfile_core/schemas/analysis_schemas/graphic_walker_schemas.py +55 -55
- flowfile_core/schemas/cloud_storage_schemas.py +59 -55
- flowfile_core/schemas/input_schema.py +398 -154
- flowfile_core/schemas/output_model.py +50 -35
- flowfile_core/schemas/schemas.py +207 -67
- flowfile_core/schemas/transform_schema.py +1360 -435
- flowfile_core/schemas/yaml_types.py +117 -0
- flowfile_core/secret_manager/secret_manager.py +17 -13
- flowfile_core/{flowfile/node_designer/data_types.py → types.py} +33 -3
- flowfile_core/utils/arrow_reader.py +7 -6
- flowfile_core/utils/excel_file_manager.py +3 -3
- flowfile_core/utils/fileManager.py +7 -7
- flowfile_core/utils/fl_executor.py +8 -10
- flowfile_core/utils/utils.py +4 -4
- flowfile_core/utils/validate_setup.py +5 -4
- flowfile_frame/__init__.py +107 -50
- flowfile_frame/adapters.py +2 -9
- flowfile_frame/adding_expr.py +73 -32
- flowfile_frame/cloud_storage/frame_helpers.py +27 -23
- flowfile_frame/cloud_storage/secret_manager.py +12 -26
- flowfile_frame/config.py +2 -5
- flowfile_frame/expr.py +311 -218
- flowfile_frame/expr.pyi +160 -159
- flowfile_frame/expr_name.py +23 -23
- flowfile_frame/flow_frame.py +581 -489
- flowfile_frame/flow_frame.pyi +123 -104
- flowfile_frame/flow_frame_methods.py +236 -252
- flowfile_frame/group_frame.py +50 -20
- flowfile_frame/join.py +2 -2
- flowfile_frame/lazy.py +129 -87
- flowfile_frame/lazy_methods.py +83 -30
- flowfile_frame/list_name_space.py +55 -50
- flowfile_frame/selectors.py +148 -68
- flowfile_frame/series.py +9 -7
- flowfile_frame/utils.py +19 -21
- flowfile_worker/__init__.py +12 -4
- flowfile_worker/configs.py +11 -19
- flowfile_worker/create/__init__.py +14 -27
- flowfile_worker/create/funcs.py +143 -94
- flowfile_worker/create/models.py +139 -68
- flowfile_worker/create/pl_types.py +14 -15
- flowfile_worker/create/read_excel_tables.py +34 -41
- flowfile_worker/create/utils.py +22 -19
- flowfile_worker/external_sources/s3_source/main.py +18 -51
- flowfile_worker/external_sources/s3_source/models.py +34 -27
- flowfile_worker/external_sources/sql_source/main.py +8 -5
- flowfile_worker/external_sources/sql_source/models.py +13 -9
- flowfile_worker/flow_logger.py +10 -8
- flowfile_worker/funcs.py +214 -155
- flowfile_worker/main.py +11 -17
- flowfile_worker/models.py +35 -28
- flowfile_worker/process_manager.py +2 -3
- flowfile_worker/routes.py +121 -93
- flowfile_worker/secrets.py +9 -6
- flowfile_worker/spawner.py +80 -49
- flowfile_worker/utils.py +3 -2
- shared/__init__.py +2 -7
- shared/storage_config.py +25 -13
- test_utils/postgres/commands.py +3 -2
- test_utils/postgres/fixtures.py +9 -9
- test_utils/s3/commands.py +1 -1
- test_utils/s3/data_generator.py +3 -4
- test_utils/s3/demo_data_generator.py +4 -7
- test_utils/s3/fixtures.py +7 -5
- tools/migrate/README.md +56 -0
- tools/migrate/__init__.py +12 -0
- tools/migrate/__main__.py +118 -0
- tools/migrate/legacy_schemas.py +682 -0
- tools/migrate/migrate.py +610 -0
- tools/migrate/tests/__init__.py +0 -0
- tools/migrate/tests/conftest.py +21 -0
- tools/migrate/tests/test_migrate.py +622 -0
- tools/migrate/tests/test_migration_e2e.py +1009 -0
- tools/migrate/tests/test_node_migrations.py +843 -0
- flowfile/web/static/assets/CloudConnectionManager-2dfdce2f.css +0 -86
- flowfile/web/static/assets/CustomNode-74a37f74.css +0 -32
- flowfile/web/static/assets/DatabaseManager-30fa27e5.css +0 -64
- flowfile/web/static/assets/Filter-812dcbca.js +0 -164
- flowfile/web/static/assets/Filter-f62091b3.css +0 -20
- flowfile/web/static/assets/ManualInput-3246a08d.css +0 -96
- flowfile/web/static/assets/PivotValidation-891ddfb0.css +0 -13
- flowfile/web/static/assets/PivotValidation-c46cd420.css +0 -13
- flowfile/web/static/assets/SliderInput-b8fb6a8c.css +0 -4
- flowfile/web/static/assets/UnpivotValidation-0d240eeb.css +0 -13
- flowfile/web/static/assets/outputCsv-9cc59e0b.css +0 -2499
- flowfile/web/static/assets/outputParquet-cf8cf3f2.css +0 -4
- flowfile/web/static/assets/secretApi-538058f3.js +0 -46
- flowfile/web/static/assets/vue-codemirror-bccfde04.css +0 -32
- flowfile-0.4.1.dist-info/RECORD +0 -376
- flowfile_core/flowfile/manage/open_flowfile.py +0 -143
- {flowfile-0.4.1.dist-info → flowfile-0.5.3.dist-info}/licenses/LICENSE +0 -0
- /flowfile_core/flowfile/manage/manage_flowfile.py → /tools/__init__.py +0 -0
flowfile_core/routes/logs.py
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import json
|
|
3
3
|
import time
|
|
4
|
+
from collections.abc import AsyncGenerator
|
|
4
5
|
from pathlib import Path
|
|
5
|
-
|
|
6
|
+
|
|
6
7
|
import aiofiles
|
|
7
|
-
from fastapi import APIRouter,
|
|
8
|
+
from fastapi import APIRouter, Depends, HTTPException
|
|
8
9
|
from fastapi.responses import StreamingResponse
|
|
9
10
|
|
|
10
|
-
from flowfile_core import ServerRun
|
|
11
|
-
from flowfile_core import
|
|
11
|
+
from flowfile_core import ServerRun, flow_file_handler
|
|
12
|
+
from flowfile_core.auth.jwt import get_current_active_user, get_current_user_from_query
|
|
13
|
+
|
|
12
14
|
# Core modules
|
|
13
15
|
from flowfile_core.configs import logger
|
|
14
16
|
from flowfile_core.configs.flow_logger import clear_all_flow_logs
|
|
15
|
-
from flowfile_core.auth.jwt import get_current_active_user, get_current_user_from_query
|
|
16
17
|
|
|
17
18
|
# Schema and models
|
|
18
19
|
from flowfile_core.schemas import schemas
|
|
@@ -20,7 +21,7 @@ from flowfile_core.schemas import schemas
|
|
|
20
21
|
router = APIRouter()
|
|
21
22
|
|
|
22
23
|
|
|
23
|
-
@router.post("/clear-logs", tags=[
|
|
24
|
+
@router.post("/clear-logs", tags=["flow_logging"])
|
|
24
25
|
async def clear_logs(current_user=Depends(get_current_active_user)):
|
|
25
26
|
clear_all_flow_logs()
|
|
26
27
|
return {"message": "All flow logs have been cleared."}
|
|
@@ -31,7 +32,7 @@ async def format_sse_message(data: str) -> str:
|
|
|
31
32
|
return f"data: {json.dumps(data)}\n\n"
|
|
32
33
|
|
|
33
34
|
|
|
34
|
-
@router.post("/logs/{flow_id}", tags=[
|
|
35
|
+
@router.post("/logs/{flow_id}", tags=["flow_logging"])
|
|
35
36
|
async def add_log(flow_id: int, log_message: str):
|
|
36
37
|
"""Adds a log message to the log file for a given flow_id."""
|
|
37
38
|
flow = flow_file_handler.get_flow(flow_id)
|
|
@@ -41,35 +42,32 @@ async def add_log(flow_id: int, log_message: str):
|
|
|
41
42
|
return {"message": "Log added successfully"}
|
|
42
43
|
|
|
43
44
|
|
|
44
|
-
@router.post("/raw_logs", tags=[
|
|
45
|
+
@router.post("/raw_logs", tags=["flow_logging"])
|
|
45
46
|
async def add_raw_log(raw_log_input: schemas.RawLogInput):
|
|
46
47
|
"""Adds a log message to the log file for a given flow_id."""
|
|
47
|
-
logger.info(
|
|
48
|
+
logger.info("Adding raw logs")
|
|
48
49
|
flow = flow_file_handler.get_flow(raw_log_input.flowfile_flow_id)
|
|
49
50
|
if not flow:
|
|
50
51
|
raise HTTPException(status_code=404, detail="Flow not found")
|
|
51
52
|
flow.flow_logger.get_log_filepath()
|
|
52
53
|
flow_logger = flow.flow_logger
|
|
53
54
|
flow_logger.get_log_filepath()
|
|
54
|
-
if raw_log_input.log_type ==
|
|
55
|
-
flow_logger.info(raw_log_input.log_message,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
flow_logger.error(raw_log_input.log_message,
|
|
59
|
-
extra=raw_log_input.extra)
|
|
55
|
+
if raw_log_input.log_type == "INFO":
|
|
56
|
+
flow_logger.info(raw_log_input.log_message, extra=raw_log_input.extra)
|
|
57
|
+
elif raw_log_input.log_type == "ERROR":
|
|
58
|
+
flow_logger.error(raw_log_input.log_message, extra=raw_log_input.extra)
|
|
60
59
|
return {"message": "Log added successfully"}
|
|
61
60
|
|
|
62
61
|
|
|
63
62
|
async def stream_log_file(
|
|
64
63
|
log_file_path: Path,
|
|
65
64
|
is_running_callable: callable,
|
|
66
|
-
idle_timeout: int = 60 # timeout in seconds
|
|
65
|
+
idle_timeout: int = 60, # timeout in seconds
|
|
67
66
|
) -> AsyncGenerator[str, None]:
|
|
68
67
|
logger.info(f"Streaming log file: {log_file_path}")
|
|
69
68
|
last_active = time.monotonic()
|
|
70
69
|
try:
|
|
71
|
-
async with aiofiles.open(log_file_path
|
|
72
|
-
|
|
70
|
+
async with aiofiles.open(log_file_path) as file:
|
|
73
71
|
# Ensure we start at the beginning
|
|
74
72
|
await file.seek(0)
|
|
75
73
|
while is_running_callable():
|
|
@@ -78,7 +76,6 @@ async def stream_log_file(
|
|
|
78
76
|
yield await format_sse_message("Server is shutting down. Closing connection.")
|
|
79
77
|
break
|
|
80
78
|
|
|
81
|
-
|
|
82
79
|
line = await file.readline()
|
|
83
80
|
if line:
|
|
84
81
|
formatted_message = await format_sse_message(line.strip())
|
|
@@ -113,21 +110,17 @@ async def stream_log_file(
|
|
|
113
110
|
raise HTTPException(status_code=500, detail=f"Error reading log file: {e}")
|
|
114
111
|
|
|
115
112
|
|
|
116
|
-
@router.get("/logs/{flow_id}", tags=[
|
|
117
|
-
async def stream_logs(
|
|
118
|
-
flow_id: int,
|
|
119
|
-
idle_timeout: int = 300,
|
|
120
|
-
current_user=Depends(get_current_user_from_query)
|
|
121
|
-
):
|
|
113
|
+
@router.get("/logs/{flow_id}", tags=["flow_logging"])
|
|
114
|
+
async def stream_logs(flow_id: int, idle_timeout: int = 300, current_user=Depends(get_current_user_from_query)):
|
|
122
115
|
"""
|
|
123
116
|
Streams logs for a given flow_id using Server-Sent Events.
|
|
124
117
|
Requires authentication via token in query parameter.
|
|
125
118
|
The connection will close gracefully if the server shuts down.
|
|
126
119
|
"""
|
|
127
120
|
logger.info(f"Starting log stream for flow_id: {flow_id} by user: {current_user.username}")
|
|
128
|
-
await asyncio.sleep(.3)
|
|
121
|
+
await asyncio.sleep(0.3)
|
|
129
122
|
flow = flow_file_handler.get_flow(flow_id)
|
|
130
|
-
logger.info(
|
|
123
|
+
logger.info("Streaming logs")
|
|
131
124
|
if not flow:
|
|
132
125
|
raise HTTPException(status_code=404, detail="Flow not found")
|
|
133
126
|
|
|
@@ -153,6 +146,5 @@ async def stream_logs(
|
|
|
153
146
|
"Cache-Control": "no-cache",
|
|
154
147
|
"Connection": "keep-alive",
|
|
155
148
|
"Content-Type": "text/event-stream",
|
|
156
|
-
}
|
|
149
|
+
},
|
|
157
150
|
)
|
|
158
|
-
|
flowfile_core/routes/public.py
CHANGED
|
@@ -5,7 +5,7 @@ from fastapi.responses import RedirectResponse
|
|
|
5
5
|
router = APIRouter()
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
@router.get("/", tags=[
|
|
8
|
+
@router.get("/", tags=["admin"])
|
|
9
9
|
async def docs_redirect():
|
|
10
|
-
"""
|
|
11
|
-
return RedirectResponse(url=
|
|
10
|
+
"""Redirects to the documentation page."""
|
|
11
|
+
return RedirectResponse(url="/docs")
|
flowfile_core/routes/routes.py
CHANGED
|
@@ -29,7 +29,9 @@ from flowfile_core.database.connection import get_db
|
|
|
29
29
|
from flowfile_core.fileExplorer.funcs import (
|
|
30
30
|
SecureFileExplorer,
|
|
31
31
|
FileInfo,
|
|
32
|
-
get_files_from_directory
|
|
32
|
+
get_files_from_directory,
|
|
33
|
+
validate_file_path,
|
|
34
|
+
validate_path_under_cwd,
|
|
33
35
|
)
|
|
34
36
|
from flowfile_core.flowfile.analytics.analytics_processor import AnalyticsProcessor
|
|
35
37
|
from flowfile_core.flowfile.code_generator.code_generator import export_flow_to_polars
|
|
@@ -76,14 +78,19 @@ async def upload_file(file: UploadFile = File(...)) -> JSONResponse:
|
|
|
76
78
|
Returns:
|
|
77
79
|
A JSON response containing the filename and the path where it was saved.
|
|
78
80
|
"""
|
|
79
|
-
|
|
81
|
+
safe_name = Path(file.filename).name.replace("..", "")
|
|
82
|
+
if not safe_name:
|
|
83
|
+
raise HTTPException(400, 'Invalid filename')
|
|
84
|
+
uploads_dir = Path("uploads")
|
|
85
|
+
uploads_dir.mkdir(exist_ok=True)
|
|
86
|
+
file_location = uploads_dir / safe_name
|
|
80
87
|
with open(file_location, "wb+") as file_object:
|
|
81
88
|
file_object.write(file.file.read())
|
|
82
|
-
return JSONResponse(content={"filename":
|
|
89
|
+
return JSONResponse(content={"filename": safe_name, "filepath": str(file_location)})
|
|
83
90
|
|
|
84
91
|
|
|
85
|
-
@router.get('/files/files_in_local_directory/', response_model=
|
|
86
|
-
async def get_local_files(directory: str) ->
|
|
92
|
+
@router.get('/files/files_in_local_directory/', response_model=list[FileInfo], tags=['file manager'])
|
|
93
|
+
async def get_local_files(directory: str) -> list[FileInfo]:
|
|
87
94
|
"""Retrieves a list of files from a specified local directory.
|
|
88
95
|
|
|
89
96
|
Args:
|
|
@@ -94,10 +101,21 @@ async def get_local_files(directory: str) -> List[FileInfo]:
|
|
|
94
101
|
|
|
95
102
|
Raises:
|
|
96
103
|
HTTPException: 404 if the directory does not exist.
|
|
104
|
+
HTTPException: 403 if access is denied (path outside sandbox).
|
|
97
105
|
"""
|
|
98
|
-
|
|
99
|
-
|
|
106
|
+
# Validate path is within sandbox before proceeding
|
|
107
|
+
explorer = SecureFileExplorer(
|
|
108
|
+
start_path=storage.user_data_directory,
|
|
109
|
+
sandbox_root=storage.user_data_directory
|
|
110
|
+
)
|
|
111
|
+
validated_path = explorer.get_absolute_path(directory)
|
|
112
|
+
if validated_path is None:
|
|
113
|
+
raise HTTPException(403, 'Access denied or directory does not exist')
|
|
114
|
+
if not validated_path.exists() or not validated_path.is_dir():
|
|
100
115
|
raise HTTPException(404, 'Directory does not exist')
|
|
116
|
+
files = get_files_from_directory(str(validated_path), sandbox_root=storage.user_data_directory)
|
|
117
|
+
if files is None:
|
|
118
|
+
raise HTTPException(403, 'Access denied or directory does not exist')
|
|
101
119
|
return files
|
|
102
120
|
|
|
103
121
|
|
|
@@ -179,9 +197,9 @@ def create_directory(new_directory: input_schema.NewDirectory) -> bool:
|
|
|
179
197
|
raise error
|
|
180
198
|
|
|
181
199
|
|
|
182
|
-
@router.post(
|
|
183
|
-
def register_flow(flow_data: schemas.FlowSettings) -> int:
|
|
184
|
-
"""Registers a new flow session with the application.
|
|
200
|
+
@router.post("/flow/register/", tags=["editor"])
|
|
201
|
+
def register_flow(flow_data: schemas.FlowSettings, current_user=Depends(get_current_active_user)) -> int:
|
|
202
|
+
"""Registers a new flow session with the application for the current user.
|
|
185
203
|
|
|
186
204
|
Args:
|
|
187
205
|
flow_data: The `FlowSettings` for the new flow.
|
|
@@ -189,13 +207,15 @@ def register_flow(flow_data: schemas.FlowSettings) -> int:
|
|
|
189
207
|
Returns:
|
|
190
208
|
The ID of the newly registered flow.
|
|
191
209
|
"""
|
|
192
|
-
|
|
210
|
+
user_id = current_user.id if current_user else None
|
|
211
|
+
return flow_file_handler.register_flow(flow_data, user_id=user_id)
|
|
193
212
|
|
|
194
213
|
|
|
195
|
-
@router.get(
|
|
196
|
-
async def get_active_flow_file_sessions() ->
|
|
197
|
-
"""Retrieves a list of all currently active flow sessions."""
|
|
198
|
-
|
|
214
|
+
@router.get("/active_flowfile_sessions/", response_model=list[schemas.FlowSettings])
|
|
215
|
+
async def get_active_flow_file_sessions(current_user=Depends(get_current_active_user)) -> list[schemas.FlowSettings]:
|
|
216
|
+
"""Retrieves a list of all currently active flow sessions for the current user."""
|
|
217
|
+
user_id = current_user.id if current_user else None
|
|
218
|
+
return [flf.flow_settings for flf in flow_file_handler.get_user_flows(user_id)]
|
|
199
219
|
|
|
200
220
|
|
|
201
221
|
@router.post("/node/trigger_fetch_data", tags=['editor'])
|
|
@@ -266,8 +286,6 @@ def get_run_status(flow_id: int, response: Response):
|
|
|
266
286
|
flow = flow_file_handler.get_flow(flow_id)
|
|
267
287
|
if not flow:
|
|
268
288
|
raise HTTPException(status_code=404, detail="Flow not found")
|
|
269
|
-
if flow.latest_run_info is None:
|
|
270
|
-
raise HTTPException(status_code=404, detail="No run information available")
|
|
271
289
|
if flow.flow_settings.is_running:
|
|
272
290
|
response.status_code = status.HTTP_202_ACCEPTED
|
|
273
291
|
else:
|
|
@@ -468,31 +486,35 @@ def get_generated_code(flow_id: int) -> str:
|
|
|
468
486
|
return export_flow_to_polars(flow)
|
|
469
487
|
|
|
470
488
|
|
|
471
|
-
@router.post(
|
|
472
|
-
def create_flow(flow_path: str = None, name: str = None):
|
|
489
|
+
@router.post("/editor/create_flow/", tags=["editor"])
|
|
490
|
+
def create_flow(flow_path: str = None, name: str = None, current_user=Depends(get_current_active_user)):
|
|
473
491
|
"""Creates a new, empty flow file at the specified path and registers a session for it."""
|
|
474
492
|
if flow_path is not None and name is None:
|
|
475
493
|
name = Path(flow_path).stem
|
|
476
494
|
elif flow_path is not None and name is not None:
|
|
477
|
-
if name not in flow_path and flow_path.endswith(".
|
|
495
|
+
if name not in flow_path and (flow_path.endswith(".yaml") or flow_path.endswith(".yml")):
|
|
478
496
|
raise HTTPException(422, 'The name must be part of the flow path when a full path is provided')
|
|
479
|
-
elif name in flow_path and not flow_path.endswith(".
|
|
480
|
-
flow_path = str(Path(flow_path) / (name + ".
|
|
481
|
-
elif name not in flow_path and name.endswith(".
|
|
497
|
+
elif name in flow_path and not (flow_path.endswith(".yaml") or flow_path.endswith(".yml")):
|
|
498
|
+
flow_path = str(Path(flow_path) / (name + ".yaml"))
|
|
499
|
+
elif name not in flow_path and (name.endswith(".yaml") or name.endswith(".yml")):
|
|
482
500
|
flow_path = str(Path(flow_path) / name)
|
|
483
|
-
elif name not in flow_path and not name.endswith(".
|
|
484
|
-
flow_path = str(Path(flow_path) / (name + ".
|
|
501
|
+
elif name not in flow_path and not (name.endswith(".yaml") or name.endswith(".yml")):
|
|
502
|
+
flow_path = str(Path(flow_path) / (name + ".yaml"))
|
|
485
503
|
if flow_path is not None:
|
|
504
|
+
# Validate path is within allowed sandbox
|
|
505
|
+
flow_path = validate_path_under_cwd(flow_path)
|
|
486
506
|
flow_path_ref = Path(flow_path)
|
|
487
507
|
if not flow_path_ref.parent.exists():
|
|
488
|
-
raise HTTPException(422,
|
|
489
|
-
|
|
508
|
+
raise HTTPException(422, "The directory does not exist")
|
|
509
|
+
user_id = current_user.id if current_user else None
|
|
510
|
+
return flow_file_handler.add_flow(name=name, flow_path=flow_path, user_id=user_id)
|
|
490
511
|
|
|
491
512
|
|
|
492
|
-
@router.post(
|
|
493
|
-
def close_flow(flow_id: int) -> None:
|
|
494
|
-
"""Closes an active flow session."""
|
|
495
|
-
|
|
513
|
+
@router.post("/editor/close_flow/", tags=["editor"])
|
|
514
|
+
def close_flow(flow_id: int, current_user=Depends(get_current_active_user)) -> None:
|
|
515
|
+
"""Closes an active flow session for the current user."""
|
|
516
|
+
user_id = current_user.id if current_user else None
|
|
517
|
+
flow_file_handler.delete_flow(flow_id, user_id=user_id)
|
|
496
518
|
|
|
497
519
|
|
|
498
520
|
@router.post('/update_settings/', tags=['transform'])
|
|
@@ -536,7 +558,15 @@ def add_generic_settings(input_data: Dict[str, Any], node_type: str, current_use
|
|
|
536
558
|
def get_list_of_saved_flows(path: str):
|
|
537
559
|
"""Scans a directory for saved flow files (`.flowfile`)."""
|
|
538
560
|
try:
|
|
539
|
-
|
|
561
|
+
# Validate path is within sandbox before proceeding
|
|
562
|
+
explorer = SecureFileExplorer(
|
|
563
|
+
start_path=storage.user_data_directory,
|
|
564
|
+
sandbox_root=storage.user_data_directory
|
|
565
|
+
)
|
|
566
|
+
validated_path = explorer.get_absolute_path(path)
|
|
567
|
+
if validated_path is None:
|
|
568
|
+
return []
|
|
569
|
+
return get_files_from_directory(str(validated_path), types=['flowfile'], sandbox_root=storage.user_data_directory)
|
|
540
570
|
except:
|
|
541
571
|
return []
|
|
542
572
|
|
|
@@ -598,18 +628,21 @@ async def get_downstream_node_ids(flow_id: int, node_id: int) -> List[int]:
|
|
|
598
628
|
return list(node.get_all_dependent_node_ids())
|
|
599
629
|
|
|
600
630
|
|
|
601
|
-
@router.get(
|
|
602
|
-
def import_saved_flow(flow_path: str) -> int:
|
|
603
|
-
"""Imports a flow from a saved `.
|
|
604
|
-
|
|
605
|
-
if not
|
|
606
|
-
raise HTTPException(404,
|
|
607
|
-
|
|
631
|
+
@router.get("/import_flow/", tags=["editor"], response_model=int)
|
|
632
|
+
def import_saved_flow(flow_path: str, current_user=Depends(get_current_active_user)) -> int:
|
|
633
|
+
"""Imports a flow from a saved `.yaml` and registers it as a new session for the current user."""
|
|
634
|
+
validated_path = validate_path_under_cwd(flow_path)
|
|
635
|
+
if not os.path.exists(validated_path):
|
|
636
|
+
raise HTTPException(404, "File not found")
|
|
637
|
+
user_id = current_user.id if current_user else None
|
|
638
|
+
return flow_file_handler.import_flow(Path(validated_path), user_id=user_id)
|
|
608
639
|
|
|
609
640
|
|
|
610
641
|
@router.get('/save_flow', tags=['editor'])
|
|
611
642
|
def save_flow(flow_id: int, flow_path: str = None):
|
|
612
|
-
"""Saves the current state of a flow to a `.
|
|
643
|
+
"""Saves the current state of a flow to a `.yaml`."""
|
|
644
|
+
if flow_path is not None:
|
|
645
|
+
flow_path = validate_path_under_cwd(flow_path)
|
|
613
646
|
flow = flow_file_handler.get_flow(flow_id)
|
|
614
647
|
flow.save_flow(flow_path=flow_path)
|
|
615
648
|
|
|
@@ -673,10 +706,11 @@ async def get_instant_function_result(flow_id: int, node_id: int, func_string: s
|
|
|
673
706
|
raise HTTPException(status_code=500, detail=str(e))
|
|
674
707
|
|
|
675
708
|
|
|
676
|
-
@router.get('/api/get_xlsx_sheet_names', tags=['excel_reader'], response_model=
|
|
677
|
-
async def get_excel_sheet_names(path: str) ->
|
|
709
|
+
@router.get('/api/get_xlsx_sheet_names', tags=['excel_reader'], response_model=list[str])
|
|
710
|
+
async def get_excel_sheet_names(path: str) -> list[str] | None:
|
|
678
711
|
"""Retrieves the sheet names from an Excel file."""
|
|
679
|
-
|
|
712
|
+
validated_path = validate_path_under_cwd(path)
|
|
713
|
+
sheet_names = excel_file_manager.get_sheet_names(validated_path)
|
|
680
714
|
if sheet_names:
|
|
681
715
|
return sheet_names
|
|
682
716
|
else:
|
flowfile_core/routes/secrets.py
CHANGED
|
@@ -5,8 +5,8 @@ This router provides secure endpoints for creating, retrieving, and deleting
|
|
|
5
5
|
sensitive credentials for the authenticated user. Secrets are encrypted before
|
|
6
6
|
being stored and are associated with the user's ID.
|
|
7
7
|
"""
|
|
8
|
+
|
|
8
9
|
import os
|
|
9
|
-
from typing import List
|
|
10
10
|
|
|
11
11
|
from fastapi import APIRouter, Depends, HTTPException
|
|
12
12
|
from sqlalchemy.orm import Session
|
|
@@ -15,12 +15,13 @@ from flowfile_core.auth.jwt import get_current_active_user
|
|
|
15
15
|
from flowfile_core.auth.models import Secret, SecretInput
|
|
16
16
|
from flowfile_core.database import models as db_models
|
|
17
17
|
from flowfile_core.database.connection import get_db
|
|
18
|
-
from flowfile_core.secret_manager.secret_manager import
|
|
18
|
+
from flowfile_core.secret_manager.secret_manager import delete_secret as delete_secret_action
|
|
19
|
+
from flowfile_core.secret_manager.secret_manager import store_secret
|
|
19
20
|
|
|
20
21
|
router = APIRouter(dependencies=[Depends(get_current_active_user)])
|
|
21
22
|
|
|
22
23
|
|
|
23
|
-
@router.get("/secrets", response_model=
|
|
24
|
+
@router.get("/secrets", response_model=list[Secret])
|
|
24
25
|
async def get_secrets(current_user=Depends(get_current_active_user), db: Session = Depends(get_db)):
|
|
25
26
|
"""Retrieves all secret names for the currently authenticated user.
|
|
26
27
|
|
|
@@ -42,18 +43,15 @@ async def get_secrets(current_user=Depends(get_current_active_user), db: Session
|
|
|
42
43
|
# Prepare response model (without decrypting)
|
|
43
44
|
secrets = []
|
|
44
45
|
for db_secret in db_secrets:
|
|
45
|
-
secrets.append(Secret(
|
|
46
|
-
name=db_secret.name,
|
|
47
|
-
value=db_secret.encrypted_value,
|
|
48
|
-
user_id=str(db_secret.user_id)
|
|
49
|
-
))
|
|
46
|
+
secrets.append(Secret(name=db_secret.name, value=db_secret.encrypted_value, user_id=str(db_secret.user_id)))
|
|
50
47
|
|
|
51
48
|
return secrets
|
|
52
49
|
|
|
53
50
|
|
|
54
51
|
@router.post("/secrets", response_model=Secret)
|
|
55
|
-
async def create_secret(
|
|
56
|
-
|
|
52
|
+
async def create_secret(
|
|
53
|
+
secret: SecretInput, current_user=Depends(get_current_active_user), db: Session = Depends(get_db)
|
|
54
|
+
) -> Secret:
|
|
57
55
|
"""Creates a new secret for the authenticated user.
|
|
58
56
|
|
|
59
57
|
The secret value is encrypted before being stored in the database. A secret
|
|
@@ -73,10 +71,11 @@ async def create_secret(secret: SecretInput, current_user=Depends(get_current_ac
|
|
|
73
71
|
# Get user ID
|
|
74
72
|
user_id = 1 if os.environ.get("FLOWFILE_MODE") == "electron" else current_user.id
|
|
75
73
|
|
|
76
|
-
existing_secret =
|
|
77
|
-
db_models.Secret
|
|
78
|
-
db_models.Secret.name == secret.name
|
|
79
|
-
|
|
74
|
+
existing_secret = (
|
|
75
|
+
db.query(db_models.Secret)
|
|
76
|
+
.filter(db_models.Secret.user_id == user_id, db_models.Secret.name == secret.name)
|
|
77
|
+
.first()
|
|
78
|
+
)
|
|
80
79
|
|
|
81
80
|
if existing_secret:
|
|
82
81
|
raise HTTPException(status_code=400, detail="Secret with this name already exists")
|
|
@@ -87,8 +86,9 @@ async def create_secret(secret: SecretInput, current_user=Depends(get_current_ac
|
|
|
87
86
|
|
|
88
87
|
|
|
89
88
|
@router.get("/secrets/{secret_name}", response_model=Secret)
|
|
90
|
-
async def get_secret(
|
|
91
|
-
|
|
89
|
+
async def get_secret(
|
|
90
|
+
secret_name: str, current_user=Depends(get_current_active_user), db: Session = Depends(get_db)
|
|
91
|
+
) -> Secret:
|
|
92
92
|
"""Retrieves a specific secret by name for the authenticated user.
|
|
93
93
|
|
|
94
94
|
Note: This endpoint returns the secret name and metadata but does not
|
|
@@ -109,24 +109,22 @@ async def get_secret(secret_name: str,
|
|
|
109
109
|
user_id = 1 if os.environ.get("FLOWFILE_MODE") == "electron" else current_user.id
|
|
110
110
|
|
|
111
111
|
# Get secret from database
|
|
112
|
-
db_secret =
|
|
113
|
-
db_models.Secret
|
|
114
|
-
db_models.Secret.name == secret_name
|
|
115
|
-
|
|
112
|
+
db_secret = (
|
|
113
|
+
db.query(db_models.Secret)
|
|
114
|
+
.filter(db_models.Secret.user_id == user_id, db_models.Secret.name == secret_name)
|
|
115
|
+
.first()
|
|
116
|
+
)
|
|
116
117
|
|
|
117
118
|
if not db_secret:
|
|
118
119
|
raise HTTPException(status_code=404, detail="Secret not found")
|
|
119
120
|
|
|
120
|
-
return Secret(
|
|
121
|
-
name=db_secret.name,
|
|
122
|
-
value=db_secret.encrypted_value,
|
|
123
|
-
user_id=str(db_secret.user_id)
|
|
124
|
-
)
|
|
121
|
+
return Secret(name=db_secret.name, value=db_secret.encrypted_value, user_id=str(db_secret.user_id))
|
|
125
122
|
|
|
126
123
|
|
|
127
124
|
@router.delete("/secrets/{secret_name}", status_code=204)
|
|
128
|
-
async def delete_secret(
|
|
129
|
-
|
|
125
|
+
async def delete_secret(
|
|
126
|
+
secret_name: str, current_user=Depends(get_current_active_user), db: Session = Depends(get_db)
|
|
127
|
+
) -> None:
|
|
130
128
|
"""Deletes a secret by name for the authenticated user.
|
|
131
129
|
|
|
132
130
|
Args:
|