Flowfile 0.5.1__py3-none-any.whl → 0.5.4__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 +194 -74
- flowfile/__main__.py +10 -7
- flowfile/api.py +51 -57
- flowfile/web/__init__.py +14 -9
- flowfile/web/static/assets/AdminView-f53bad23.css +129 -0
- flowfile/web/static/assets/AdminView-f9847d67.js +713 -0
- flowfile/web/static/assets/CloudConnectionView-cf85f943.css +72 -0
- flowfile/web/static/assets/{CloudConnectionManager-0dfba9f2.js → CloudConnectionView-faace55b.js} +11 -11
- flowfile/web/static/assets/{CloudStorageReader-29d14fcc.css → CloudStorageReader-24c54524.css} +27 -27
- flowfile/web/static/assets/{CloudStorageReader-d5b1b6c9.js → CloudStorageReader-d86ecaa7.js} +10 -8
- flowfile/web/static/assets/{CloudStorageWriter-00d87aad.js → CloudStorageWriter-0f4d9a44.js} +10 -8
- flowfile/web/static/assets/{CloudStorageWriter-b0ee067f.css → CloudStorageWriter-60547855.css} +26 -26
- flowfile/web/static/assets/ColumnActionInput-c44b7aee.css +159 -0
- flowfile/web/static/assets/ColumnActionInput-f4189ae0.js +330 -0
- flowfile/web/static/assets/{ColumnSelector-47996a16.css → ColumnSelector-371637fb.css} +2 -2
- flowfile/web/static/assets/{ColumnSelector-4685e75d.js → ColumnSelector-e66b33da.js} +3 -5
- flowfile/web/static/assets/ContextMenu-49463352.js +9 -0
- flowfile/web/static/assets/ContextMenu-dd5f3f25.js +9 -0
- flowfile/web/static/assets/ContextMenu-f709b884.js +9 -0
- flowfile/web/static/assets/ContextMenu.vue_vue_type_script_setup_true_lang-a1bd6314.js +59 -0
- flowfile/web/static/assets/{CrossJoin-702a3edd.js → CrossJoin-24694b8f.js} +12 -10
- flowfile/web/static/assets/{CrossJoin-1119d18e.css → CrossJoin-71b4cc10.css} +20 -20
- flowfile/web/static/assets/{CustomNode-b1519993.js → CustomNode-569d45ff.js} +43 -24
- flowfile/web/static/assets/CustomNode-edb9b939.css +42 -0
- flowfile/web/static/assets/{DatabaseConnectionSettings-0c04b2e5.css → DatabaseConnectionSettings-c20a1e16.css} +23 -21
- flowfile/web/static/assets/{DatabaseConnectionSettings-6f3e4ea5.js → DatabaseConnectionSettings-cfc08938.js} +5 -4
- flowfile/web/static/assets/{DatabaseReader-ae61773c.css → DatabaseReader-5bf8c75b.css} +41 -46
- flowfile/web/static/assets/{DatabaseReader-d38c7295.js → DatabaseReader-701feabb.js} +25 -15
- flowfile/web/static/assets/{DatabaseManager-cf5ef661.js → DatabaseView-0482e5b5.js} +11 -11
- flowfile/web/static/assets/DatabaseView-6655afd6.css +57 -0
- flowfile/web/static/assets/{DatabaseWriter-b04ef46a.js → DatabaseWriter-16721989.js} +17 -10
- flowfile/web/static/assets/{DatabaseWriter-2f570e53.css → DatabaseWriter-bdcf2c8b.css} +29 -27
- flowfile/web/static/assets/{designer-8da3ba3a.css → DesignerView-49abb835.css} +783 -663
- flowfile/web/static/assets/{designer-9633482a.js → DesignerView-f64749fb.js} +1292 -3253
- flowfile/web/static/assets/{documentation-ca400224.js → DocumentationView-61bd2990.js} +5 -5
- flowfile/web/static/assets/{documentation-12216a74.css → DocumentationView-9ea6e871.css} +9 -9
- flowfile/web/static/assets/{ExploreData-2d0cf4db.css → ExploreData-10c5acc8.css} +13 -12
- flowfile/web/static/assets/{ExploreData-5fa10ed8.js → ExploreData-e2735b13.js} +18 -9
- flowfile/web/static/assets/{ExternalSource-d39af878.js → ExternalSource-2535c3b2.js} +9 -7
- flowfile/web/static/assets/{ExternalSource-e37b6275.css → ExternalSource-7ac7373f.css} +20 -20
- flowfile/web/static/assets/Filter-2cdbc93c.js +287 -0
- flowfile/web/static/assets/Filter-7494ea97.css +48 -0
- flowfile/web/static/assets/{Formula-bb96803d.css → Formula-53d58c43.css} +7 -7
- flowfile/web/static/assets/{Formula-6b04fb1d.js → Formula-fcda3c2c.js} +13 -11
- flowfile/web/static/assets/{FuzzyMatch-1010f966.css → FuzzyMatch-ad6361d6.css} +68 -69
- flowfile/web/static/assets/{FuzzyMatch-999521f4.js → FuzzyMatch-f8d3b7d3.js} +12 -10
- flowfile/web/static/assets/{Pivot-cf333e3d.css → GraphSolver-4b4d7db9.css} +5 -5
- flowfile/web/static/assets/{GraphSolver-17dd2198.js → GraphSolver-72eaa695.js} +14 -12
- flowfile/web/static/assets/GroupBy-5792782d.css +9 -0
- flowfile/web/static/assets/{GroupBy-6b039e18.js → GroupBy-8aa0598b.js} +9 -7
- flowfile/web/static/assets/{Join-fd79b451.css → Join-28b5e18f.css} +22 -22
- flowfile/web/static/assets/{Join-24d0f113.js → Join-e40f0ffa.js} +13 -11
- flowfile/web/static/assets/LoginView-5111c9ae.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-34639209.js → ManualInput-9b6f3224.js} +170 -116
- flowfile/web/static/assets/{MultiSelect-0e8724a3.js → MultiSelect-ef28e19e.js} +2 -2
- flowfile/web/static/assets/{MultiSelect.vue_vue_type_script_setup_true_lang-b0e538c2.js → MultiSelect.vue_vue_type_script_setup_true_lang-83b3bbfd.js} +1 -1
- flowfile/web/static/assets/NodeDesigner-94cd4dd3.css +1429 -0
- flowfile/web/static/assets/NodeDesigner-d2b7ee2b.js +2712 -0
- flowfile/web/static/assets/{NumericInput-3d63a470.js → NumericInput-1d789794.js} +2 -2
- flowfile/web/static/assets/{NumericInput.vue_vue_type_script_setup_true_lang-e0edeccc.js → NumericInput.vue_vue_type_script_setup_true_lang-7775f83e.js} +5 -2
- flowfile/web/static/assets/Output-692dd25d.css +37 -0
- flowfile/web/static/assets/{Output-edea9802.js → Output-cefef801.js} +14 -10
- flowfile/web/static/assets/{GraphSolver-f0cb7bfb.css → Pivot-0eda81b4.css} +5 -5
- flowfile/web/static/assets/{Pivot-61d19301.js → Pivot-bab1b75b.js} +12 -10
- flowfile/web/static/assets/PivotValidation-0e905b1a.css +13 -0
- flowfile/web/static/assets/PivotValidation-41b57ad6.css +13 -0
- flowfile/web/static/assets/{PivotValidation-f97fec5b.js → PivotValidation-e7941f91.js} +3 -3
- flowfile/web/static/assets/{PivotValidation-de9f43fe.js → PivotValidation-fba09336.js} +3 -3
- flowfile/web/static/assets/{PolarsCode-650322d1.css → PolarsCode-2b1f1f23.css} +4 -4
- flowfile/web/static/assets/{PolarsCode-bc3c9984.js → PolarsCode-740e40fa.js} +18 -9
- flowfile/web/static/assets/PopOver-862d7e28.js +939 -0
- flowfile/web/static/assets/PopOver-d96599db.css +33 -0
- flowfile/web/static/assets/{Read-64a3f259.js → Read-225cc63f.js} +16 -12
- flowfile/web/static/assets/{Read-e808b239.css → Read-90f366bc.css} +15 -15
- flowfile/web/static/assets/{RecordCount-3d5039be.js → RecordCount-ffc71eca.js} +6 -4
- flowfile/web/static/assets/{RecordId-597510e0.js → RecordId-a70bb8df.js} +9 -7
- flowfile/web/static/assets/{SQLQueryComponent-df51adbe.js → SQLQueryComponent-15a421f5.js} +3 -3
- flowfile/web/static/assets/SQLQueryComponent-edb90b98.css +29 -0
- flowfile/web/static/assets/{Sample-4be0a507.js → Sample-6c26afc7.js} +6 -4
- flowfile/web/static/assets/SecretSelector-6329f743.css +43 -0
- flowfile/web/static/assets/SecretSelector-ceed9496.js +113 -0
- flowfile/web/static/assets/{SecretManager-4839be57.js → SecretsView-214d255a.js} +35 -36
- flowfile/web/static/assets/SecretsView-aa291340.css +38 -0
- flowfile/web/static/assets/{Select-9b72f201.js → Select-8fc29999.js} +9 -7
- 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-7ded385d.js → SettingsSection-3f70e4c3.js} +3 -3
- flowfile/web/static/assets/{SettingsSection-f0f75a42.js → SettingsSection-83090218.js} +3 -3
- flowfile/web/static/assets/{SettingsSection-2e4d03c4.css → SettingsSection-8f980839.css} +4 -4
- flowfile/web/static/assets/{SettingsSection-e1e9c953.js → SettingsSection-9f0d1725.js} +3 -3
- flowfile/web/static/assets/SetupView-3fa0aa03.js +160 -0
- flowfile/web/static/assets/SetupView-e2da3442.css +230 -0
- flowfile/web/static/assets/{SingleSelect-6c777aac.js → SingleSelect-a4a568cb.js} +2 -2
- flowfile/web/static/assets/{SingleSelect.vue_vue_type_script_setup_true_lang-33e3ff9b.js → SingleSelect.vue_vue_type_script_setup_true_lang-c8ebdd33.js} +1 -1
- flowfile/web/static/assets/{SliderInput-7cb93e62.js → SliderInput-be533e71.js} +7 -4
- flowfile/web/static/assets/SliderInput-f2e4f23c.css +4 -0
- flowfile/web/static/assets/{Sort-6cbde21a.js → Sort-154dad81.js} +9 -7
- flowfile/web/static/assets/Sort-4abb7fae.css +9 -0
- flowfile/web/static/assets/{TextInput-d9a40c11.js → TextInput-454e2bda.js} +2 -2
- flowfile/web/static/assets/{TextInput.vue_vue_type_script_setup_true_lang-5896c375.js → TextInput.vue_vue_type_script_setup_true_lang-e86510d0.js} +5 -2
- flowfile/web/static/assets/{TextToRows-5d2c1190.css → TextToRows-12afb4f4.css} +10 -10
- flowfile/web/static/assets/{TextToRows-c4fcbf4d.js → TextToRows-ea73433d.js} +11 -10
- flowfile/web/static/assets/{ToggleSwitch-4ef91d19.js → ToggleSwitch-9d7b30f1.js} +2 -2
- flowfile/web/static/assets/{ToggleSwitch.vue_vue_type_script_setup_true_lang-38478c20.js → ToggleSwitch.vue_vue_type_script_setup_true_lang-00f2580e.js} +1 -1
- flowfile/web/static/assets/{UnavailableFields-5edd5322.css → UnavailableFields-394a1f78.css} +14 -14
- flowfile/web/static/assets/{UnavailableFields-a03f512c.js → UnavailableFields-b72a2c72.js} +4 -4
- flowfile/web/static/assets/{Union-bfe9b996.js → Union-1e44f263.js} +8 -6
- flowfile/web/static/assets/{Union-af6c3d9b.css → Union-d6a8d7d5.css} +7 -7
- flowfile/web/static/assets/Unique-2b705521.css +3 -0
- flowfile/web/static/assets/{Unique-5d023a27.js → Unique-a3bc6d0a.js} +13 -10
- flowfile/web/static/assets/{Unpivot-1e422df3.css → Unpivot-b6ad6427.css} +7 -7
- flowfile/web/static/assets/{Unpivot-91cc5354.js → Unpivot-e27935fc.js} +11 -9
- flowfile/web/static/assets/{UnpivotValidation-7ee2de44.js → UnpivotValidation-72497680.js} +3 -3
- flowfile/web/static/assets/UnpivotValidation-d5ca3b7b.css +13 -0
- flowfile/web/static/assets/{VueGraphicWalker-ed5ab88b.css → VueGraphicWalker-430f0b86.css} +1 -1
- flowfile/web/static/assets/{VueGraphicWalker-e51b9924.js → VueGraphicWalker-d9ab70a3.js} +4 -4
- flowfile/web/static/assets/{api-cf1221f0.js → api-a2102880.js} +1 -1
- flowfile/web/static/assets/{api-c1bad5ca.js → api-f75042b0.js} +1 -1
- flowfile/web/static/assets/{dropDown-35135ba8.css → dropDown-1d6acbd9.css} +41 -41
- flowfile/web/static/assets/{dropDown-614b998d.js → dropDown-2798a109.js} +3 -3
- flowfile/web/static/assets/{fullEditor-f7971590.js → fullEditor-cf7d7d93.js} +11 -10
- flowfile/web/static/assets/{fullEditor-178376bb.css → fullEditor-fe9f7e18.css} +77 -65
- flowfile/web/static/assets/{genericNodeSettings-4fe5f36b.js → genericNodeSettings-14eac1c3.js} +5 -5
- flowfile/web/static/assets/{genericNodeSettings-924759c7.css → genericNodeSettings-3b2507ea.css} +10 -10
- flowfile/web/static/assets/{index-5429bbf8.js → index-387a6f18.js} +41806 -40958
- flowfile/web/static/assets/index-6b367bb5.js +38 -0
- flowfile/web/static/assets/{index-50508d4d.css → index-e96ab018.css} +2184 -569
- flowfile/web/static/assets/index-f0a6e5a5.js +2696 -0
- flowfile/web/static/assets/node.types-2c15bb7e.js +82 -0
- flowfile/web/static/assets/nodeInput-ed2ae8d7.js +2 -0
- flowfile/web/static/assets/{outputCsv-076b85ab.js → outputCsv-3c1757e8.js} +3 -3
- flowfile/web/static/assets/outputCsv-b9a072af.css +2499 -0
- flowfile/web/static/assets/{outputExcel-0fd17dbe.js → outputExcel-686e1f48.js} +3 -3
- flowfile/web/static/assets/{outputExcel-b41305c0.css → outputExcel-f5d272b2.css} +26 -26
- flowfile/web/static/assets/outputParquet-54597c3c.css +4 -0
- flowfile/web/static/assets/{outputParquet-b61e0847.js → outputParquet-df28faa7.js} +4 -4
- flowfile/web/static/assets/{readCsv-c767cb37.css → readCsv-3bfac4c3.css} +15 -15
- flowfile/web/static/assets/{readCsv-a8bb8b61.js → readCsv-e37eee21.js} +3 -3
- flowfile/web/static/assets/{readExcel-806d2826.css → readExcel-3db6b763.css} +13 -13
- flowfile/web/static/assets/{readExcel-67b4aee0.js → readExcel-a13f14bb.js} +5 -5
- flowfile/web/static/assets/{readParquet-92ce1dbc.js → readParquet-344cf746.js} +3 -3
- flowfile/web/static/assets/{readParquet-48c81530.css → readParquet-c5244ad5.css} +4 -4
- flowfile/web/static/assets/secrets.api-ae198c5c.js +65 -0
- flowfile/web/static/assets/{selectDynamic-92e25ee3.js → selectDynamic-6b4b0767.js} +5 -5
- flowfile/web/static/assets/{selectDynamic-aa913ff4.css → selectDynamic-f2fb394f.css} +21 -20
- flowfile/web/static/assets/{vue-codemirror.esm-41b0e0d7.js → vue-codemirror.esm-31ba0e0b.js} +31 -640
- flowfile/web/static/assets/{vue-content-loader.es-2c8e608f.js → vue-content-loader.es-4469c8ff.js} +1 -1
- flowfile/web/static/index.html +2 -2
- {flowfile-0.5.1.dist-info → flowfile-0.5.4.dist-info}/METADATA +3 -4
- flowfile-0.5.4.dist-info/RECORD +407 -0
- flowfile_core/__init__.py +13 -6
- 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 +64 -19
- 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 +145 -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 +26 -24
- flowfile_core/flowfile/analytics/graphic_walker.py +11 -12
- flowfile_core/flowfile/analytics/utils.py +1 -1
- flowfile_core/flowfile/code_generator/__init__.py +11 -0
- flowfile_core/flowfile/code_generator/code_generator.py +706 -247
- 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 +115 -83
- flowfile_core/flowfile/flow_data_engine/flow_data_engine.py +493 -423
- 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 +31 -20
- 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 +14 -15
- 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 +190 -127
- flowfile_core/flowfile/flow_data_engine/threaded_processes.py +8 -8
- flowfile_core/flowfile/flow_data_engine/utils.py +99 -67
- flowfile_core/flowfile/flow_graph.py +920 -571
- flowfile_core/flowfile/flow_graph_utils.py +31 -49
- flowfile_core/flowfile/flow_node/flow_node.py +379 -258
- flowfile_core/flowfile/flow_node/models.py +53 -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 +80 -30
- flowfile_core/flowfile/manage/compatibility_enhancements.py +209 -126
- flowfile_core/flowfile/manage/io_flowfile.py +54 -57
- flowfile_core/flowfile/node_designer/__init__.py +19 -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 +278 -34
- flowfile_core/flowfile/schema_callbacks.py +71 -51
- 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 +64 -53
- 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 +46 -4
- flowfile_core/routes/routes.py +70 -34
- 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 +96 -66
- flowfile_core/schemas/input_schema.py +231 -144
- flowfile_core/schemas/output_model.py +49 -34
- flowfile_core/schemas/schemas.py +116 -89
- flowfile_core/schemas/transform_schema.py +518 -263
- flowfile_core/schemas/yaml_types.py +21 -7
- flowfile_core/secret_manager/secret_manager.py +123 -18
- flowfile_core/types.py +29 -9
- 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 +117 -51
- 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/database/__init__.py +36 -0
- flowfile_frame/database/connection_manager.py +205 -0
- flowfile_frame/database/frame_helpers.py +249 -0
- flowfile_frame/expr.py +311 -218
- flowfile_frame/expr.pyi +160 -159
- flowfile_frame/expr_name.py +23 -23
- flowfile_frame/flow_frame.py +571 -476
- flowfile_frame/flow_frame.pyi +123 -104
- flowfile_frame/flow_frame_methods.py +227 -246
- 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 -7
- flowfile_worker/configs.py +41 -33
- flowfile_worker/create/__init__.py +14 -9
- flowfile_worker/create/funcs.py +114 -77
- flowfile_worker/create/models.py +46 -43
- 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 -90
- flowfile_worker/secrets.py +114 -21
- flowfile_worker/spawner.py +89 -54
- 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/__init__.py +1 -1
- tools/migrate/__main__.py +16 -29
- tools/migrate/legacy_schemas.py +251 -190
- tools/migrate/migrate.py +193 -181
- tools/migrate/tests/conftest.py +1 -3
- tools/migrate/tests/test_migrate.py +36 -41
- tools/migrate/tests/test_migration_e2e.py +28 -29
- tools/migrate/tests/test_node_migrations.py +50 -20
- flowfile/web/static/assets/CloudConnectionManager-2dfdce2f.css +0 -86
- flowfile/web/static/assets/ContextMenu-23e909da.js +0 -41
- flowfile/web/static/assets/ContextMenu-4c74eef1.css +0 -26
- flowfile/web/static/assets/ContextMenu-63cfa99b.css +0 -26
- flowfile/web/static/assets/ContextMenu-70ae0c79.js +0 -41
- flowfile/web/static/assets/ContextMenu-c13f91d0.css +0 -26
- flowfile/web/static/assets/ContextMenu-f149cf7c.js +0 -41
- flowfile/web/static/assets/CustomNode-74a37f74.css +0 -32
- flowfile/web/static/assets/DatabaseManager-30fa27e5.css +0 -64
- flowfile/web/static/assets/Filter-9b6d08db.js +0 -164
- flowfile/web/static/assets/Filter-f62091b3.css +0 -20
- flowfile/web/static/assets/GroupBy-b9505323.css +0 -51
- flowfile/web/static/assets/ManualInput-3246a08d.css +0 -96
- flowfile/web/static/assets/Output-283fe388.css +0 -37
- flowfile/web/static/assets/PivotValidation-891ddfb0.css +0 -13
- flowfile/web/static/assets/PivotValidation-c46cd420.css +0 -13
- flowfile/web/static/assets/SQLQueryComponent-36cef432.css +0 -27
- flowfile/web/static/assets/SliderInput-b8fb6a8c.css +0 -4
- flowfile/web/static/assets/Sort-3643d625.css +0 -51
- flowfile/web/static/assets/Unique-f9fb0809.css +0 -51
- flowfile/web/static/assets/UnpivotValidation-0d240eeb.css +0 -13
- flowfile/web/static/assets/nodeInput-5d0d6b79.js +0 -41
- flowfile/web/static/assets/outputCsv-9cc59e0b.css +0 -2499
- flowfile/web/static/assets/outputParquet-cf8cf3f2.css +0 -4
- flowfile/web/static/assets/secretApi-68435402.js +0 -46
- flowfile/web/static/assets/vue-codemirror-bccfde04.css +0 -32
- flowfile-0.5.1.dist-info/RECORD +0 -388
- {flowfile-0.5.1.dist-info → flowfile-0.5.4.dist-info}/WHEEL +0 -0
- {flowfile-0.5.1.dist-info → flowfile-0.5.4.dist-info}/entry_points.txt +0 -0
- {flowfile-0.5.1.dist-info → flowfile-0.5.4.dist-info}/licenses/LICENSE +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
|
@@ -1,11 +1,53 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
1
3
|
from fastapi import APIRouter
|
|
2
4
|
from fastapi.responses import RedirectResponse
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
from flowfile_core.auth.secrets import generate_master_key, is_master_key_configured
|
|
3
8
|
|
|
4
|
-
# Router setup
|
|
5
9
|
router = APIRouter()
|
|
6
10
|
|
|
7
11
|
|
|
8
|
-
|
|
12
|
+
class SetupStatus(BaseModel):
|
|
13
|
+
"""Response model for the setup status endpoint."""
|
|
14
|
+
|
|
15
|
+
setup_required: bool
|
|
16
|
+
master_key_configured: bool
|
|
17
|
+
mode: str
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class GeneratedKey(BaseModel):
|
|
21
|
+
"""Response model for the generate key endpoint."""
|
|
22
|
+
|
|
23
|
+
key: str
|
|
24
|
+
instructions: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@router.get("/", tags=["admin"])
|
|
9
28
|
async def docs_redirect():
|
|
10
|
-
"""
|
|
11
|
-
return RedirectResponse(url=
|
|
29
|
+
"""Redirects to the documentation page."""
|
|
30
|
+
return RedirectResponse(url="/docs")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@router.get("/health/status", response_model=SetupStatus, tags=["health"])
|
|
34
|
+
async def get_setup_status():
|
|
35
|
+
"""Get the current setup status of the application."""
|
|
36
|
+
mode = os.environ.get("FLOWFILE_MODE", "electron")
|
|
37
|
+
master_key_ok = is_master_key_configured()
|
|
38
|
+
return SetupStatus(
|
|
39
|
+
setup_required=not master_key_ok,
|
|
40
|
+
master_key_configured=master_key_ok,
|
|
41
|
+
mode=mode,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@router.post("/setup/generate-key", response_model=GeneratedKey, tags=["setup"])
|
|
46
|
+
async def generate_key():
|
|
47
|
+
"""Generate a new master encryption key."""
|
|
48
|
+
key = generate_master_key()
|
|
49
|
+
instructions = (
|
|
50
|
+
f'Add to your .env file:\n FLOWFILE_MASTER_KEY="{key}"\n\n'
|
|
51
|
+
"Then restart: docker-compose down && docker-compose up"
|
|
52
|
+
)
|
|
53
|
+
return GeneratedKey(key=key, instructions=instructions)
|
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'])
|
|
@@ -466,8 +486,8 @@ def get_generated_code(flow_id: int) -> str:
|
|
|
466
486
|
return export_flow_to_polars(flow)
|
|
467
487
|
|
|
468
488
|
|
|
469
|
-
@router.post(
|
|
470
|
-
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)):
|
|
471
491
|
"""Creates a new, empty flow file at the specified path and registers a session for it."""
|
|
472
492
|
if flow_path is not None and name is None:
|
|
473
493
|
name = Path(flow_path).stem
|
|
@@ -481,16 +501,20 @@ def create_flow(flow_path: str = None, name: str = None):
|
|
|
481
501
|
elif name not in flow_path and not (name.endswith(".yaml") or name.endswith(".yml")):
|
|
482
502
|
flow_path = str(Path(flow_path) / (name + ".yaml"))
|
|
483
503
|
if flow_path is not None:
|
|
504
|
+
# Validate path is within allowed sandbox
|
|
505
|
+
flow_path = validate_path_under_cwd(flow_path)
|
|
484
506
|
flow_path_ref = Path(flow_path)
|
|
485
507
|
if not flow_path_ref.parent.exists():
|
|
486
|
-
raise HTTPException(422,
|
|
487
|
-
|
|
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)
|
|
488
511
|
|
|
489
512
|
|
|
490
|
-
@router.post(
|
|
491
|
-
def close_flow(flow_id: int) -> None:
|
|
492
|
-
"""Closes an active flow session."""
|
|
493
|
-
|
|
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)
|
|
494
518
|
|
|
495
519
|
|
|
496
520
|
@router.post('/update_settings/', tags=['transform'])
|
|
@@ -534,7 +558,15 @@ def add_generic_settings(input_data: Dict[str, Any], node_type: str, current_use
|
|
|
534
558
|
def get_list_of_saved_flows(path: str):
|
|
535
559
|
"""Scans a directory for saved flow files (`.flowfile`)."""
|
|
536
560
|
try:
|
|
537
|
-
|
|
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)
|
|
538
570
|
except:
|
|
539
571
|
return []
|
|
540
572
|
|
|
@@ -596,18 +628,21 @@ async def get_downstream_node_ids(flow_id: int, node_id: int) -> List[int]:
|
|
|
596
628
|
return list(node.get_all_dependent_node_ids())
|
|
597
629
|
|
|
598
630
|
|
|
599
|
-
@router.get(
|
|
600
|
-
def import_saved_flow(flow_path: str) -> int:
|
|
601
|
-
"""Imports a flow from a saved `.yaml` and registers it as a new session."""
|
|
602
|
-
|
|
603
|
-
if not
|
|
604
|
-
raise HTTPException(404,
|
|
605
|
-
|
|
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)
|
|
606
639
|
|
|
607
640
|
|
|
608
641
|
@router.get('/save_flow', tags=['editor'])
|
|
609
642
|
def save_flow(flow_id: int, flow_path: str = None):
|
|
610
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)
|
|
611
646
|
flow = flow_file_handler.get_flow(flow_id)
|
|
612
647
|
flow.save_flow(flow_path=flow_path)
|
|
613
648
|
|
|
@@ -671,10 +706,11 @@ async def get_instant_function_result(flow_id: int, node_id: int, func_string: s
|
|
|
671
706
|
raise HTTPException(status_code=500, detail=str(e))
|
|
672
707
|
|
|
673
708
|
|
|
674
|
-
@router.get('/api/get_xlsx_sheet_names', tags=['excel_reader'], response_model=
|
|
675
|
-
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:
|
|
676
711
|
"""Retrieves the sheet names from an Excel file."""
|
|
677
|
-
|
|
712
|
+
validated_path = validate_path_under_cwd(path)
|
|
713
|
+
sheet_names = excel_file_manager.get_sheet_names(validated_path)
|
|
678
714
|
if sheet_names:
|
|
679
715
|
return sheet_names
|
|
680
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:
|