Flowfile 0.5.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 +178 -74
- flowfile/__main__.py +10 -7
- flowfile/api.py +51 -57
- 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-0dfba9f2.js → CloudConnectionView-f13f202b.js} +11 -11
- flowfile/web/static/assets/{CloudStorageReader-d5b1b6c9.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-00d87aad.js → CloudStorageWriter-8e781e11.js} +10 -8
- flowfile/web/static/assets/{ColumnSelector-47996a16.css → ColumnSelector-371637fb.css} +2 -2
- flowfile/web/static/assets/{ColumnSelector-4685e75d.js → ColumnSelector-8ad68ea9.js} +3 -5
- flowfile/web/static/assets/{ContextMenu-c13f91d0.css → ContextMenu-26d4dd27.css} +6 -6
- flowfile/web/static/assets/{ContextMenu-23e909da.js → ContextMenu-31ee57f0.js} +3 -3
- flowfile/web/static/assets/{ContextMenu-70ae0c79.js → ContextMenu-69a74055.js} +3 -3
- flowfile/web/static/assets/{ContextMenu-f149cf7c.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-702a3edd.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-b1519993.js → CustomNode-8479239b.js} +36 -24
- flowfile/web/static/assets/{DatabaseConnectionSettings-6f3e4ea5.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-d38c7295.js → DatabaseReader-c58b9552.js} +25 -15
- flowfile/web/static/assets/DatabaseView-6655afd6.css +57 -0
- flowfile/web/static/assets/{DatabaseManager-cf5ef661.js → DatabaseView-d26a9140.js} +11 -11
- flowfile/web/static/assets/{DatabaseWriter-2f570e53.css → DatabaseWriter-217a99f1.css} +19 -19
- flowfile/web/static/assets/{DatabaseWriter-b04ef46a.js → DatabaseWriter-4d05ddc7.js} +17 -10
- flowfile/web/static/assets/{designer-8da3ba3a.css → DesignerView-a6d0ee84.css} +614 -546
- flowfile/web/static/assets/{designer-9633482a.js → DesignerView-e6f5c0e8.js} +1107 -3170
- flowfile/web/static/assets/{documentation-ca400224.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-5fa10ed8.js → ExploreData-7b54caca.js} +18 -9
- flowfile/web/static/assets/{ExternalSource-d39af878.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-6b04fb1d.js → Formula-aac42b1e.js} +13 -11
- flowfile/web/static/assets/{FuzzyMatch-1010f966.css → FuzzyMatch-ad6361d6.css} +68 -69
- flowfile/web/static/assets/{FuzzyMatch-999521f4.js → FuzzyMatch-cd9bbfca.js} +12 -10
- flowfile/web/static/assets/{Pivot-cf333e3d.css → GraphSolver-c24dec17.css} +5 -5
- flowfile/web/static/assets/{GraphSolver-17dd2198.js → GraphSolver-c7e6780e.js} +13 -11
- flowfile/web/static/assets/{GroupBy-6b039e18.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-24d0f113.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-34639209.js → ManualInput-8d3374b2.js} +170 -116
- flowfile/web/static/assets/{MultiSelect-0e8724a3.js → MultiSelect-ad1b6243.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-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-3d63a470.js → NumericInput-7100234c.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-5130219f.js} +5 -2
- flowfile/web/static/assets/{Output-283fe388.css → Output-35e97000.css} +6 -6
- flowfile/web/static/assets/{Output-edea9802.js → Output-f5efd2aa.js} +12 -9
- flowfile/web/static/assets/{GraphSolver-f0cb7bfb.css → Pivot-0eda81b4.css} +5 -5
- flowfile/web/static/assets/{Pivot-61d19301.js → Pivot-d981d23c.js} +11 -9
- flowfile/web/static/assets/PivotValidation-0e905b1a.css +13 -0
- flowfile/web/static/assets/{PivotValidation-f97fec5b.js → PivotValidation-39386e95.js} +3 -3
- flowfile/web/static/assets/PivotValidation-41b57ad6.css +13 -0
- flowfile/web/static/assets/{PivotValidation-de9f43fe.js → PivotValidation-63de1f73.js} +3 -3
- flowfile/web/static/assets/{PolarsCode-650322d1.css → PolarsCode-2b1f1f23.css} +4 -4
- flowfile/web/static/assets/{PolarsCode-bc3c9984.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-e808b239.css → Read-36e7bd51.css} +12 -12
- flowfile/web/static/assets/{Read-64a3f259.js → Read-aec2e377.js} +14 -11
- flowfile/web/static/assets/{RecordCount-3d5039be.js → RecordCount-78ed6845.js} +6 -4
- flowfile/web/static/assets/{RecordId-597510e0.js → RecordId-2156e890.js} +8 -6
- flowfile/web/static/assets/{SQLQueryComponent-36cef432.css → SQLQueryComponent-1c2f26b4.css} +5 -5
- flowfile/web/static/assets/{SQLQueryComponent-df51adbe.js → SQLQueryComponent-48c72f5b.js} +3 -3
- flowfile/web/static/assets/{Sample-4be0a507.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-4839be57.js → SecretsView-17df66ee.js} +35 -36
- flowfile/web/static/assets/SecretsView-aa291340.css +38 -0
- flowfile/web/static/assets/{Select-9b72f201.js → Select-0aee4c54.js} +9 -7
- flowfile/web/static/assets/{SettingsSection-f0f75a42.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-e1e9c953.js → SettingsSection-cd341bb6.js} +3 -3
- flowfile/web/static/assets/{SettingsSection-7ded385d.js → SettingsSection-f2002a6d.js} +3 -3
- flowfile/web/static/assets/{SingleSelect-6c777aac.js → SingleSelect-460cc0ea.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-30741bb2.js} +1 -1
- flowfile/web/static/assets/{SliderInput-7cb93e62.js → SliderInput-5d926864.js} +7 -4
- flowfile/web/static/assets/SliderInput-f2e4f23c.css +4 -0
- flowfile/web/static/assets/{Sort-6cbde21a.js → Sort-3cdc971b.js} +9 -7
- flowfile/web/static/assets/{Unique-f9fb0809.css → Sort-8a871341.css} +10 -10
- flowfile/web/static/assets/{TextInput-d9a40c11.js → TextInput-a2d0bfbd.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-abad1ca2.js} +5 -2
- flowfile/web/static/assets/{TextToRows-5d2c1190.css → TextToRows-12afb4f4.css} +10 -10
- flowfile/web/static/assets/{TextToRows-c4fcbf4d.js → TextToRows-918945f7.js} +11 -10
- flowfile/web/static/assets/{ToggleSwitch-4ef91d19.js → ToggleSwitch-f0ef5196.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-5605c793.js} +1 -1
- flowfile/web/static/assets/{UnavailableFields-5edd5322.css → UnavailableFields-54d2f518.css} +6 -6
- flowfile/web/static/assets/{UnavailableFields-a03f512c.js → UnavailableFields-bdad6144.js} +4 -4
- flowfile/web/static/assets/{Union-af6c3d9b.css → Union-d6a8d7d5.css} +7 -7
- flowfile/web/static/assets/{Union-bfe9b996.js → Union-e8ab8c86.js} +8 -6
- flowfile/web/static/assets/{Unique-5d023a27.js → Unique-8cd4f976.js} +13 -10
- 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-91cc5354.js → Unpivot-8da14095.js} +10 -8
- flowfile/web/static/assets/{UnpivotValidation-7ee2de44.js → UnpivotValidation-6f7d89ff.js} +3 -3
- flowfile/web/static/assets/UnpivotValidation-d5ca3b7b.css +13 -0
- flowfile/web/static/assets/{VueGraphicWalker-e51b9924.js → VueGraphicWalker-3fb312e1.js} +4 -4
- flowfile/web/static/assets/{VueGraphicWalker-ed5ab88b.css → VueGraphicWalker-430f0b86.css} +1 -1
- flowfile/web/static/assets/{api-cf1221f0.js → api-24483f0d.js} +1 -1
- flowfile/web/static/assets/{api-c1bad5ca.js → api-8b81fa73.js} +1 -1
- flowfile/web/static/assets/{dropDown-35135ba8.css → dropDown-3d8dc5fa.css} +40 -40
- flowfile/web/static/assets/{dropDown-614b998d.js → dropDown-ac0fda9d.js} +3 -3
- flowfile/web/static/assets/{fullEditor-f7971590.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-4fe5f36b.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-5429bbf8.js → index-fb6493ae.js} +41626 -40867
- flowfile/web/static/assets/node.types-2c15bb7e.js +82 -0
- flowfile/web/static/assets/nodeInput-0eb13f1a.js +2 -0
- flowfile/web/static/assets/{outputCsv-076b85ab.js → outputCsv-8f8ba42d.js} +3 -3
- flowfile/web/static/assets/outputCsv-b9a072af.css +2499 -0
- flowfile/web/static/assets/{outputExcel-0fd17dbe.js → outputExcel-393f4fef.js} +3 -3
- flowfile/web/static/assets/{outputExcel-b41305c0.css → outputExcel-f5d272b2.css} +26 -26
- flowfile/web/static/assets/{outputParquet-b61e0847.js → outputParquet-07c81f65.js} +4 -4
- flowfile/web/static/assets/outputParquet-54597c3c.css +4 -0
- flowfile/web/static/assets/{readCsv-a8bb8b61.js → readCsv-07f6d9ad.js} +3 -3
- flowfile/web/static/assets/{readCsv-c767cb37.css → readCsv-3bfac4c3.css} +15 -15
- flowfile/web/static/assets/{readExcel-806d2826.css → readExcel-3db6b763.css} +13 -13
- flowfile/web/static/assets/{readExcel-67b4aee0.js → readExcel-ed69bc8f.js} +5 -5
- flowfile/web/static/assets/{readParquet-48c81530.css → readParquet-c5244ad5.css} +4 -4
- flowfile/web/static/assets/{readParquet-92ce1dbc.js → readParquet-e3ed4528.js} +3 -3
- flowfile/web/static/assets/secrets.api-002e7d7e.js +65 -0
- flowfile/web/static/assets/{selectDynamic-92e25ee3.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-41b0e0d7.js → vue-codemirror.esm-0965f39f.js} +31 -640
- flowfile/web/static/assets/{vue-content-loader.es-2c8e608f.js → vue-content-loader.es-c506ad97.js} +1 -1
- flowfile/web/static/index.html +2 -2
- {flowfile-0.5.1.dist-info → flowfile-0.5.3.dist-info}/METADATA +2 -3
- flowfile-0.5.3.dist-info/RECORD +402 -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 +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 +26 -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 +358 -244
- 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 +481 -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 +918 -571
- flowfile_core/flowfile/flow_graph_utils.py +31 -49
- flowfile_core/flowfile/flow_node/flow_node.py +330 -233
- 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 +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 +135 -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 +3 -3
- 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 +59 -53
- 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 +17 -13
- 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 +106 -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/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 +11 -19
- 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 +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/__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/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/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/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.3.dist-info}/WHEEL +0 -0
- {flowfile-0.5.1.dist-info → flowfile-0.5.3.dist-info}/entry_points.txt +0 -0
- {flowfile-0.5.1.dist-info → flowfile-0.5.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
import sys
|
|
2
1
|
import importlib.util
|
|
3
2
|
import inspect
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from typing import Dict, Type, List
|
|
6
3
|
import logging
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
7
6
|
|
|
8
|
-
from flowfile_core.flowfile.node_designer.custom_node import CustomNodeBase
|
|
7
|
+
from flowfile_core.flowfile.node_designer.custom_node import CustomNodeBase
|
|
9
8
|
from shared import storage
|
|
10
9
|
|
|
11
|
-
|
|
12
10
|
logger = logging.getLogger(__name__)
|
|
13
11
|
|
|
14
12
|
|
|
15
|
-
def get_all_custom_nodes() ->
|
|
13
|
+
def get_all_custom_nodes() -> dict[str, type[CustomNodeBase]]:
|
|
16
14
|
"""
|
|
17
15
|
Scan the user-defined nodes directory and import all CustomNodeBase subclasses.
|
|
18
16
|
|
|
@@ -55,12 +53,9 @@ def get_all_custom_nodes() -> Dict[str, Type[CustomNodeBase]]:
|
|
|
55
53
|
for name, obj in inspect.getmembers(module):
|
|
56
54
|
# Check if it's a class and a subclass of CustomNodeBase
|
|
57
55
|
# but not CustomNodeBase itself
|
|
58
|
-
if
|
|
59
|
-
issubclass(obj, CustomNodeBase) and
|
|
60
|
-
obj is not CustomNodeBase):
|
|
61
|
-
|
|
56
|
+
if inspect.isclass(obj) and issubclass(obj, CustomNodeBase) and obj is not CustomNodeBase:
|
|
62
57
|
# Use the node_name attribute if it exists, otherwise use class name
|
|
63
|
-
node_name = getattr(obj,
|
|
58
|
+
node_name = getattr(obj, "node_name", name)
|
|
64
59
|
custom_nodes[node_name] = obj
|
|
65
60
|
print(f"Loaded custom node: {node_name} from {file_path.name}")
|
|
66
61
|
|
|
@@ -72,7 +67,7 @@ def get_all_custom_nodes() -> Dict[str, Type[CustomNodeBase]]:
|
|
|
72
67
|
return custom_nodes
|
|
73
68
|
|
|
74
69
|
|
|
75
|
-
def get_all_custom_nodes_with_validation() ->
|
|
70
|
+
def get_all_custom_nodes_with_validation() -> dict[str, type[CustomNodeBase]]:
|
|
76
71
|
"""
|
|
77
72
|
Enhanced version that validates the nodes before adding them.
|
|
78
73
|
"""
|
|
@@ -97,22 +92,19 @@ def get_all_custom_nodes_with_validation() -> Dict[str, Type[CustomNodeBase]]:
|
|
|
97
92
|
spec.loader.exec_module(module)
|
|
98
93
|
|
|
99
94
|
for name, obj in inspect.getmembers(module):
|
|
100
|
-
if
|
|
101
|
-
issubclass(obj, CustomNodeBase) and
|
|
102
|
-
obj is not CustomNodeBase):
|
|
103
|
-
|
|
95
|
+
if inspect.isclass(obj) and issubclass(obj, CustomNodeBase) and obj is not CustomNodeBase:
|
|
104
96
|
try:
|
|
105
97
|
_obj = obj()
|
|
106
98
|
# Validate that the node has required attributes
|
|
107
|
-
if not hasattr(_obj,
|
|
99
|
+
if not hasattr(_obj, "node_name"):
|
|
108
100
|
logger.error(f"Warning: {name} missing node_name attribute")
|
|
109
101
|
raise ValueError(f"Node {name} must implement a node_name attribute")
|
|
110
102
|
|
|
111
|
-
if not hasattr(_obj,
|
|
103
|
+
if not hasattr(_obj, "settings_schema"):
|
|
112
104
|
logger.error(f"Warning: {name} missing settings_schema attribute")
|
|
113
105
|
raise ValueError(f"Node {name} must implement a settings_schema attribute")
|
|
114
106
|
|
|
115
|
-
if not hasattr(_obj,
|
|
107
|
+
if not hasattr(_obj, "process"):
|
|
116
108
|
logger.error(f"Warning: {name} missing process method")
|
|
117
109
|
raise ValueError(f"Node {name} must implement a process method")
|
|
118
110
|
if not (storage.user_defined_nodes_icons / _obj.node_icon).exists():
|
|
@@ -137,7 +129,7 @@ def get_all_custom_nodes_with_validation() -> Dict[str, Type[CustomNodeBase]]:
|
|
|
137
129
|
return custom_nodes
|
|
138
130
|
|
|
139
131
|
|
|
140
|
-
def get_custom_nodes_lazy() ->
|
|
132
|
+
def get_custom_nodes_lazy() -> list[type[CustomNodeBase]]:
|
|
141
133
|
"""
|
|
142
134
|
Returns a list of custom node classes without instantiating them.
|
|
143
135
|
Useful for registration or catalog purposes.
|
|
@@ -162,10 +154,12 @@ def get_custom_nodes_lazy() -> List[Type[CustomNodeBase]]:
|
|
|
162
154
|
spec.loader.exec_module(module)
|
|
163
155
|
|
|
164
156
|
for name, obj in inspect.getmembers(module):
|
|
165
|
-
if (
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
157
|
+
if (
|
|
158
|
+
inspect.isclass(obj)
|
|
159
|
+
and issubclass(obj, CustomNodeBase)
|
|
160
|
+
and obj is not CustomNodeBase
|
|
161
|
+
and obj.__module__ == module.__name__
|
|
162
|
+
): # Only get classes defined in this module
|
|
169
163
|
nodes.append(obj)
|
|
170
164
|
|
|
171
165
|
except Exception as e:
|
|
@@ -176,18 +170,105 @@ def get_custom_nodes_lazy() -> List[Type[CustomNodeBase]]:
|
|
|
176
170
|
|
|
177
171
|
|
|
178
172
|
# Example usage function that matches your original pattern
|
|
179
|
-
def add_custom_node(node_class:
|
|
173
|
+
def add_custom_node(node_class: type[CustomNodeBase], registry: dict[str, type[CustomNodeBase]]):
|
|
180
174
|
"""Add a single custom node to the registry."""
|
|
181
|
-
if hasattr(node_class,
|
|
175
|
+
if hasattr(node_class, "node_name"):
|
|
182
176
|
registry[node_class.node_name] = node_class
|
|
183
177
|
else:
|
|
184
178
|
registry[node_class.__name__] = node_class
|
|
185
179
|
|
|
186
180
|
|
|
187
|
-
def get_all_nodes_from_standard_location() ->
|
|
181
|
+
def get_all_nodes_from_standard_location() -> dict[str, type[CustomNodeBase]]:
|
|
188
182
|
"""
|
|
189
183
|
Main function to get all custom nodes from the standard location.
|
|
190
184
|
This matches your original function signature.
|
|
191
185
|
"""
|
|
192
186
|
|
|
193
187
|
return get_all_custom_nodes_with_validation()
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def load_single_node_from_file(file_path: Path) -> type[CustomNodeBase] | None:
|
|
191
|
+
"""
|
|
192
|
+
Load a single custom node from a specific file.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
file_path: Path to the Python file containing the custom node
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
The custom node class if found and valid, None otherwise
|
|
199
|
+
"""
|
|
200
|
+
if not file_path.exists():
|
|
201
|
+
logger.error(f"File not found: {file_path}")
|
|
202
|
+
return None
|
|
203
|
+
|
|
204
|
+
try:
|
|
205
|
+
# Create a unique module name to avoid conflicts with cached modules
|
|
206
|
+
module_name = f"custom_node_{file_path.stem}_{id(file_path)}"
|
|
207
|
+
|
|
208
|
+
# Remove old module from sys.modules if it exists (for reloading)
|
|
209
|
+
old_module_name = file_path.stem
|
|
210
|
+
if old_module_name in sys.modules:
|
|
211
|
+
del sys.modules[old_module_name]
|
|
212
|
+
|
|
213
|
+
spec = importlib.util.spec_from_file_location(module_name, file_path)
|
|
214
|
+
|
|
215
|
+
if spec and spec.loader:
|
|
216
|
+
module = importlib.util.module_from_spec(spec)
|
|
217
|
+
sys.modules[module_name] = module
|
|
218
|
+
spec.loader.exec_module(module)
|
|
219
|
+
|
|
220
|
+
for name, obj in inspect.getmembers(module):
|
|
221
|
+
if inspect.isclass(obj) and issubclass(obj, CustomNodeBase) and obj is not CustomNodeBase:
|
|
222
|
+
try:
|
|
223
|
+
_obj = obj()
|
|
224
|
+
# Validate required attributes
|
|
225
|
+
if not hasattr(_obj, "node_name"):
|
|
226
|
+
raise ValueError(f"Node {name} must have a node_name attribute")
|
|
227
|
+
if not hasattr(_obj, "settings_schema"):
|
|
228
|
+
raise ValueError(f"Node {name} must have a settings_schema attribute")
|
|
229
|
+
if not hasattr(_obj, "process"):
|
|
230
|
+
raise ValueError(f"Node {name} must have a process method")
|
|
231
|
+
|
|
232
|
+
logger.info(f"✓ Loaded: {_obj.node_name} from {file_path.name}")
|
|
233
|
+
return obj
|
|
234
|
+
except Exception as e:
|
|
235
|
+
logger.error(f"Error validating node {name}: {e}")
|
|
236
|
+
raise
|
|
237
|
+
|
|
238
|
+
except SyntaxError as e:
|
|
239
|
+
logger.error(f"Syntax error in {file_path}: {e}")
|
|
240
|
+
raise
|
|
241
|
+
except ImportError as e:
|
|
242
|
+
logger.error(f"Import error in {file_path}: {e}")
|
|
243
|
+
raise
|
|
244
|
+
except Exception as e:
|
|
245
|
+
logger.error(f"Unexpected error loading {file_path}: {e}")
|
|
246
|
+
raise
|
|
247
|
+
|
|
248
|
+
return None
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def unload_node_by_name(node_name: str) -> bool:
|
|
252
|
+
"""
|
|
253
|
+
Remove a node from sys.modules cache by its name.
|
|
254
|
+
This helps ensure the node can be cleanly reloaded later.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
node_name: The name of the node to unload
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
True if any modules were removed, False otherwise
|
|
261
|
+
"""
|
|
262
|
+
# Convert node name to potential module names
|
|
263
|
+
module_stem = node_name.lower().replace(" ", "_")
|
|
264
|
+
|
|
265
|
+
# Find and remove any matching modules from sys.modules
|
|
266
|
+
modules_to_remove = [
|
|
267
|
+
key for key in sys.modules.keys() if key == module_stem or key.startswith(f"custom_node_{module_stem}")
|
|
268
|
+
]
|
|
269
|
+
|
|
270
|
+
for mod_name in modules_to_remove:
|
|
271
|
+
del sys.modules[mod_name]
|
|
272
|
+
logger.info(f"Removed module from cache: {mod_name}")
|
|
273
|
+
|
|
274
|
+
return len(modules_to_remove) > 0
|
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
|
|
2
1
|
# flowfile_core/flowfile_core/configs/settings.py
|
|
3
|
-
import
|
|
2
|
+
import argparse
|
|
4
3
|
import os
|
|
4
|
+
import platform
|
|
5
5
|
import tempfile
|
|
6
|
-
import argparse
|
|
7
6
|
|
|
8
7
|
from passlib.context import CryptContext
|
|
9
8
|
from starlette.config import Config
|
|
10
|
-
from shared.storage_config import storage
|
|
11
9
|
|
|
12
10
|
from flowfile_core.configs.utils import MutableBool
|
|
13
|
-
|
|
11
|
+
from shared.storage_config import storage
|
|
14
12
|
|
|
15
13
|
# Constants for server and worker configuration
|
|
16
14
|
DEFAULT_SERVER_HOST = "0.0.0.0"
|
|
@@ -27,12 +25,8 @@ OFFLOAD_TO_WORKER: MutableBool = MutableBool(os.environ.get("FLOWFILE_OFFLOAD_TO
|
|
|
27
25
|
def parse_args():
|
|
28
26
|
"""Parse command line arguments"""
|
|
29
27
|
parser = argparse.ArgumentParser(description="Flowfile Backend Server")
|
|
30
|
-
parser.add_argument(
|
|
31
|
-
|
|
32
|
-
)
|
|
33
|
-
parser.add_argument(
|
|
34
|
-
"--port", type=int, default=DEFAULT_SERVER_PORT, help="Port to bind to"
|
|
35
|
-
)
|
|
28
|
+
parser.add_argument("--host", type=str, default=DEFAULT_SERVER_HOST, help="Host to bind to")
|
|
29
|
+
parser.add_argument("--port", type=int, default=DEFAULT_SERVER_PORT, help="Port to bind to")
|
|
36
30
|
parser.add_argument(
|
|
37
31
|
"--worker-port",
|
|
38
32
|
type=int,
|
|
@@ -84,8 +78,9 @@ args = parse_args()
|
|
|
84
78
|
|
|
85
79
|
SERVER_HOST = args.host if args.host is not None else DEFAULT_SERVER_HOST
|
|
86
80
|
SERVER_PORT = args.port if args.port is not None else DEFAULT_SERVER_PORT
|
|
87
|
-
WORKER_PORT =
|
|
88
|
-
|
|
81
|
+
WORKER_PORT = (
|
|
82
|
+
args.worker_port if args.worker_port is not None else int(os.getenv("FLOWFILE_WORKER_PORT", DEFAULT_WORKER_PORT))
|
|
83
|
+
)
|
|
89
84
|
WORKER_HOST = os.getenv("WORKER_HOST", "0.0.0.0" if platform.system() != "Windows" else "127.0.0.1")
|
|
90
85
|
|
|
91
86
|
config = Config(".env")
|
|
@@ -93,9 +88,27 @@ DEBUG: bool = config("DEBUG", cast=bool, default=False)
|
|
|
93
88
|
FILE_LOCATION = config("FILE_LOCATION", cast=str, default=".\\files\\")
|
|
94
89
|
AVAILABLE_RAM = config("AVAILABLE_RAM", cast=int, default=8)
|
|
95
90
|
WORKER_URL = config("FLOWFILE_WORKER_URL", cast=str, default=get_default_worker_url(WORKER_PORT))
|
|
96
|
-
IS_RUNNING_IN_DOCKER = os.getenv('RUNNING_IN_DOCKER', 'false').lower() == 'true'
|
|
97
91
|
TEMP_DIR = storage.temp_directory
|
|
98
92
|
|
|
93
|
+
# FLOWFILE_MODE: Determines the runtime environment
|
|
94
|
+
# Possible values: "electron" (desktop app), "package" (Python package), "docker" (container)
|
|
95
|
+
FLOWFILE_MODE = os.getenv("FLOWFILE_MODE", "electron")
|
|
96
|
+
|
|
97
|
+
def is_docker_mode() -> bool:
|
|
98
|
+
"""Check if running in Docker container mode"""
|
|
99
|
+
return FLOWFILE_MODE == "docker"
|
|
100
|
+
|
|
101
|
+
def is_electron_mode() -> bool:
|
|
102
|
+
"""Check if running in Electron desktop app mode"""
|
|
103
|
+
return FLOWFILE_MODE == "electron"
|
|
104
|
+
|
|
105
|
+
def is_package_mode() -> bool:
|
|
106
|
+
"""Check if running as Python package"""
|
|
107
|
+
return FLOWFILE_MODE == "package"
|
|
108
|
+
|
|
109
|
+
# Legacy compatibility - will be removed in future versions
|
|
110
|
+
IS_RUNNING_IN_DOCKER = is_docker_mode()
|
|
111
|
+
|
|
99
112
|
ALGORITHM = "HS256"
|
|
100
113
|
ACCESS_TOKEN_EXPIRE_MINUTES = 120
|
|
101
|
-
PWD_CONTEXT = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
|
114
|
+
PWD_CONTEXT = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
from sqlalchemy import create_engine
|
|
2
|
-
from contextlib import contextmanager
|
|
3
|
-
from sqlalchemy.orm import sessionmaker
|
|
4
1
|
import os
|
|
5
2
|
import sys
|
|
3
|
+
from contextlib import contextmanager
|
|
6
4
|
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from sqlalchemy import create_engine
|
|
7
|
+
from sqlalchemy.orm import sessionmaker
|
|
8
|
+
|
|
7
9
|
from flowfile_core.configs import logger
|
|
8
10
|
from shared.storage_config import storage
|
|
9
11
|
|
|
@@ -44,8 +46,7 @@ def get_database_path() -> Path:
|
|
|
44
46
|
|
|
45
47
|
# Create database engine
|
|
46
48
|
engine = create_engine(
|
|
47
|
-
get_database_url(),
|
|
48
|
-
connect_args={"check_same_thread": False} if "sqlite" in get_database_url() else {}
|
|
49
|
+
get_database_url(), connect_args={"check_same_thread": False} if "sqlite" in get_database_url() else {}
|
|
49
50
|
)
|
|
50
51
|
|
|
51
52
|
# Create session factory
|
|
@@ -77,5 +78,5 @@ def get_database_info():
|
|
|
77
78
|
"url": get_database_url(),
|
|
78
79
|
"path": str(get_database_path()) if get_database_path() else None,
|
|
79
80
|
"app_data_dir": str(get_app_data_dir()),
|
|
80
|
-
"platform": sys.platform
|
|
81
|
+
"platform": sys.platform,
|
|
81
82
|
}
|
|
@@ -1,13 +1,53 @@
|
|
|
1
1
|
# Generate a random secure password and hash it
|
|
2
|
+
import os
|
|
2
3
|
import secrets
|
|
3
4
|
import string
|
|
5
|
+
import logging
|
|
4
6
|
from sqlalchemy.orm import Session
|
|
7
|
+
from sqlalchemy import text
|
|
5
8
|
from flowfile_core.database import models as db_models
|
|
6
9
|
from flowfile_core.database.connection import engine, SessionLocal
|
|
10
|
+
from flowfile_core.auth.password import get_password_hash
|
|
7
11
|
|
|
8
12
|
from passlib.context import CryptContext
|
|
9
13
|
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
|
10
14
|
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def run_migrations():
|
|
19
|
+
"""Run database migrations to update schema for existing databases."""
|
|
20
|
+
with engine.connect() as conn:
|
|
21
|
+
# Check if users table exists
|
|
22
|
+
result = conn.execute(text(
|
|
23
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name='users'"
|
|
24
|
+
))
|
|
25
|
+
if not result.fetchone():
|
|
26
|
+
logger.info("Users table does not exist, will be created with new schema")
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
# Check existing columns
|
|
30
|
+
result = conn.execute(text("PRAGMA table_info(users)"))
|
|
31
|
+
columns = [row[1] for row in result.fetchall()]
|
|
32
|
+
|
|
33
|
+
# Add is_admin column if missing
|
|
34
|
+
if 'is_admin' not in columns:
|
|
35
|
+
logger.info("Adding is_admin column to users table")
|
|
36
|
+
conn.execute(text("ALTER TABLE users ADD COLUMN is_admin BOOLEAN DEFAULT 0"))
|
|
37
|
+
conn.commit()
|
|
38
|
+
logger.info("Migration complete: is_admin column added")
|
|
39
|
+
|
|
40
|
+
# Add must_change_password column if missing
|
|
41
|
+
if 'must_change_password' not in columns:
|
|
42
|
+
logger.info("Adding must_change_password column to users table")
|
|
43
|
+
conn.execute(text("ALTER TABLE users ADD COLUMN must_change_password BOOLEAN DEFAULT 0"))
|
|
44
|
+
conn.commit()
|
|
45
|
+
logger.info("Migration complete: must_change_password column added")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# Run migrations BEFORE create_all to update existing tables
|
|
49
|
+
run_migrations()
|
|
50
|
+
# Then create any new tables (this will include is_admin for new databases)
|
|
11
51
|
db_models.Base.metadata.create_all(bind=engine)
|
|
12
52
|
|
|
13
53
|
|
|
@@ -21,19 +61,73 @@ def create_default_local_user(db: Session):
|
|
|
21
61
|
username="local_user",
|
|
22
62
|
email="local@flowfile.app",
|
|
23
63
|
full_name="Local User",
|
|
24
|
-
hashed_password=hashed_password
|
|
64
|
+
hashed_password=hashed_password,
|
|
65
|
+
must_change_password=False # Local user doesn't need to change password
|
|
25
66
|
)
|
|
26
67
|
db.add(local_user)
|
|
27
68
|
db.commit()
|
|
28
69
|
return True
|
|
29
|
-
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def create_docker_admin_user(db: Session):
|
|
74
|
+
"""
|
|
75
|
+
Create admin user for Docker mode from environment variables.
|
|
76
|
+
Only runs when FLOWFILE_MODE=docker.
|
|
77
|
+
Reads FLOWFILE_ADMIN_USER and FLOWFILE_ADMIN_PASSWORD from environment.
|
|
78
|
+
"""
|
|
79
|
+
# Only run in Docker mode
|
|
80
|
+
if os.environ.get("FLOWFILE_MODE") != "docker":
|
|
30
81
|
return False
|
|
31
82
|
|
|
83
|
+
# Read environment variables
|
|
84
|
+
admin_username = os.environ.get("FLOWFILE_ADMIN_USER")
|
|
85
|
+
admin_password = os.environ.get("FLOWFILE_ADMIN_PASSWORD")
|
|
86
|
+
|
|
87
|
+
# Skip if either is not set
|
|
88
|
+
if not admin_username or not admin_password:
|
|
89
|
+
logger.warning(
|
|
90
|
+
"Docker mode detected but FLOWFILE_ADMIN_USER or FLOWFILE_ADMIN_PASSWORD "
|
|
91
|
+
"not set. Admin user will not be created."
|
|
92
|
+
)
|
|
93
|
+
return False
|
|
94
|
+
|
|
95
|
+
# Check if user already exists
|
|
96
|
+
existing_user = db.query(db_models.User).filter(
|
|
97
|
+
db_models.User.username == admin_username
|
|
98
|
+
).first()
|
|
99
|
+
|
|
100
|
+
if existing_user:
|
|
101
|
+
# Ensure existing admin user has is_admin=True
|
|
102
|
+
if not existing_user.is_admin:
|
|
103
|
+
existing_user.is_admin = True
|
|
104
|
+
db.commit()
|
|
105
|
+
logger.info(f"Admin user '{admin_username}' updated with admin privileges.")
|
|
106
|
+
else:
|
|
107
|
+
logger.info(f"Admin user '{admin_username}' already exists with admin privileges.")
|
|
108
|
+
return False
|
|
109
|
+
|
|
110
|
+
# Create user with hashed password and admin privileges
|
|
111
|
+
hashed_password = get_password_hash(admin_password)
|
|
112
|
+
admin_user = db_models.User(
|
|
113
|
+
username=admin_username,
|
|
114
|
+
email=f"{admin_username}@flowfile.app",
|
|
115
|
+
full_name="Admin User",
|
|
116
|
+
hashed_password=hashed_password,
|
|
117
|
+
is_admin=True,
|
|
118
|
+
must_change_password=True # Force password change on first login
|
|
119
|
+
)
|
|
120
|
+
db.add(admin_user)
|
|
121
|
+
db.commit()
|
|
122
|
+
logger.info(f"Admin user '{admin_username}' created successfully.")
|
|
123
|
+
return True
|
|
124
|
+
|
|
32
125
|
|
|
33
126
|
def init_db():
|
|
34
127
|
db = SessionLocal()
|
|
35
128
|
try:
|
|
36
129
|
create_default_local_user(db)
|
|
130
|
+
create_docker_admin_user(db)
|
|
37
131
|
finally:
|
|
38
132
|
db.close()
|
|
39
133
|
|
flowfile_core/database/models.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, Text
|
|
1
|
+
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String, Text
|
|
2
2
|
from sqlalchemy.ext.declarative import declarative_base
|
|
3
3
|
from sqlalchemy.sql import func
|
|
4
4
|
|
|
@@ -14,6 +14,8 @@ class User(Base):
|
|
|
14
14
|
full_name = Column(String)
|
|
15
15
|
hashed_password = Column(String)
|
|
16
16
|
disabled = Column(Boolean, default=False)
|
|
17
|
+
is_admin = Column(Boolean, default=False)
|
|
18
|
+
must_change_password = Column(Boolean, default=True)
|
|
17
19
|
|
|
18
20
|
|
|
19
21
|
class Secret(Base):
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from .funcs import (
|
|
2
|
+
FileExplorer,
|
|
3
|
+
FileInfo,
|
|
4
|
+
SecureFileExplorer,
|
|
5
|
+
get_files_from_directory,
|
|
6
|
+
validate_file_path,
|
|
7
|
+
validate_path_under_cwd,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"FileExplorer",
|
|
12
|
+
"FileInfo",
|
|
13
|
+
"SecureFileExplorer",
|
|
14
|
+
"get_files_from_directory",
|
|
15
|
+
"validate_file_path",
|
|
16
|
+
"validate_path_under_cwd",
|
|
17
|
+
]
|