Flowfile 0.5.6__py3-none-any.whl → 0.6.1__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.
- flowfile/api.py +8 -6
- flowfile/web/static/assets/{AdminView-c2c7942b.js → AdminView-C4K1DdHI.js} +28 -33
- flowfile/web/static/assets/{CloudConnectionView-7a3042c6.js → CloudConnectionView-BZbPvPUL.js} +39 -50
- flowfile/web/static/assets/{CloudStorageReader-24c54524.css → CloudStorageReader-BDByiqPI.css} +25 -25
- flowfile/web/static/assets/{CloudStorageReader-709c4037.js → CloudStorageReader-DLVukNJ7.js} +30 -35
- flowfile/web/static/assets/{CloudStorageWriter-604c51a8.js → CloudStorageWriter-Bfi-C1QW.js} +32 -37
- flowfile/web/static/assets/{CloudStorageWriter-60547855.css → CloudStorageWriter-y8jL8yjG.css} +24 -24
- flowfile/web/static/assets/{ColumnActionInput-d63d6746.js → ColumnActionInput-BpiCApw9.js} +7 -12
- flowfile/web/static/assets/{ColumnSelector-0c8cd1cd.js → ColumnSelector-CEAwedI7.js} +1 -2
- flowfile/web/static/assets/ContextMenu-CdojQu0w.js +9 -0
- flowfile/web/static/assets/ContextMenu-D12mhsy1.js +9 -0
- flowfile/web/static/assets/ContextMenu-EWUR98va.js +9 -0
- flowfile/web/static/assets/{ContextMenu.vue_vue_type_script_setup_true_lang-774c517c.js → ContextMenu.vue_vue_type_script_setup_true_lang-I4rXXd6G.js} +4 -5
- flowfile/web/static/assets/{CrossJoin-38e5b99a.js → CrossJoin-BOFfxkJO.js} +19 -18
- flowfile/web/static/assets/{CrossJoin-71b4cc10.css → CrossJoin-Cmbyt9im.css} +18 -18
- flowfile/web/static/assets/{CustomNode-76e8f3f5.js → CustomNode-Bhpezobq.js} +12 -17
- flowfile/web/static/assets/{DatabaseConnectionSettings-38155669.js → DatabaseConnectionSettings-Dw3bSJKB.js} +10 -11
- flowfile/web/static/assets/{DatabaseReader-5bf8c75b.css → DatabaseReader-D6pUNUCs.css} +21 -21
- flowfile/web/static/assets/{DatabaseReader-2e549c8f.js → DatabaseReader-m87ghlw0.js} +36 -34
- flowfile/web/static/assets/{DatabaseView-dc877c29.js → DatabaseView-CisSAtpe.js} +30 -38
- flowfile/web/static/assets/{DatabaseWriter-ffb91864.js → DatabaseWriter-Bbj9JLdL.js} +33 -35
- flowfile/web/static/assets/{DatabaseWriter-bdcf2c8b.css → DatabaseWriter-RBqdFLj8.css} +17 -17
- flowfile/web/static/assets/{DesignerView-a4466dab.js → DesignerView-DemDevTQ.js} +1752 -2054
- flowfile/web/static/assets/{DesignerView-71d4e9a1.css → DesignerView-Dm6OzlIc.css} +209 -168
- flowfile/web/static/assets/{DocumentationView-979afc84.js → DocumentationView-BrC1ZR3H.js} +3 -4
- flowfile/web/static/assets/{ExploreData-e4b92aaf.js → ExploreData-BMKcDuRb.js} +8 -10
- flowfile/web/static/assets/{ExternalSource-d08e7227.js → ExternalSource-BXrNNS-f.js} +40 -42
- flowfile/web/static/assets/{ExternalSource-7ac7373f.css → ExternalSource-NB6WVl5R.css} +14 -14
- flowfile/web/static/assets/{Filter-7add806d.js → Filter-C2MjsN6P.js} +36 -33
- flowfile/web/static/assets/{Filter-7494ea97.css → Filter-DCMGGuGC.css} +9 -9
- flowfile/web/static/assets/{Formula-53d58c43.css → Formula-BYafbDj8.css} +4 -4
- flowfile/web/static/assets/{Formula-36ab24d2.js → Formula-ufuy4mVD.js} +27 -26
- flowfile/web/static/assets/{FuzzyMatch-ad6361d6.css → FuzzyMatch-BGJAwgd0.css} +42 -42
- flowfile/web/static/assets/{FuzzyMatch-cc01bb04.js → FuzzyMatch-BOHODq3h.js} +36 -38
- flowfile/web/static/assets/{GraphSolver-4fb98f3b.js → GraphSolver-B6ZzpNGO.js} +23 -21
- flowfile/web/static/assets/{GraphSolver-4b4d7db9.css → GraphSolver-DFN83sj3.css} +4 -4
- flowfile/web/static/assets/{GroupBy-b3c8f429.js → GroupBy-B9BRNcfe.js} +30 -29
- flowfile/web/static/assets/{Sort-4abb7fae.css → GroupBy-x4ooP5np.css} +1 -1
- flowfile/web/static/assets/Join-Bx_g5bZz.css +118 -0
- flowfile/web/static/assets/{Join-096b7b26.js → Join-DsBEy1IH.js} +48 -43
- flowfile/web/static/assets/{LoginView-c33a246a.js → LoginView-Ct0rhdcO.js} +1 -2
- flowfile/web/static/assets/{ManualInput-39111f19.css → ManualInput-DlZmtMdt.css} +48 -48
- flowfile/web/static/assets/{ManualInput-7307e9b1.js → ManualInput-bC4BUgnG.js} +40 -41
- flowfile/web/static/assets/{MultiSelect-14822c48.js → MultiSelect-DIQ8PuTC.js} +2 -2
- flowfile/web/static/assets/{MultiSelect.vue_vue_type_script_setup_true_lang-90c4d340.js → MultiSelect.vue_vue_type_script_setup_true_lang-BefHfqTI.js} +1 -1
- flowfile/web/static/assets/{NodeDesigner-5036c392.js → NodeDesigner-D39yzr2k.js} +178 -208
- flowfile/web/static/assets/{NodeDesigner-94cd4dd3.css → NodeDesigner-R0l6sYyY.css} +76 -76
- flowfile/web/static/assets/{NumericInput-15cf3b72.js → NumericInput-DMSX3oOr.js} +2 -2
- flowfile/web/static/assets/{NumericInput.vue_vue_type_script_setup_true_lang-91e679d7.js → NumericInput.vue_vue_type_script_setup_true_lang-d0YlVHAl.js} +1 -1
- flowfile/web/static/assets/{Output-1f8ed42c.js → Output-D0VoXGcW.js} +26 -34
- flowfile/web/static/assets/{Output-692dd25d.css → Output-DsmglIDy.css} +5 -5
- flowfile/web/static/assets/{Pivot-0e153f4e.js → Pivot-BnMB4sEe.js} +26 -26
- flowfile/web/static/assets/{Pivot-0eda81b4.css → Pivot-qKTyWxop.css} +4 -4
- flowfile/web/static/assets/{PivotValidation-81ec2a33.js → PivotValidation-B2lWvugt.js} +7 -9
- flowfile/web/static/assets/{PivotValidation-5a4f7c79.js → PivotValidation-BPlhRjpL.js} +7 -9
- flowfile/web/static/assets/{PolarsCode-a39f15ac.js → PolarsCode-5h0tHnWR.js} +22 -20
- flowfile/web/static/assets/{PopOver-ddcfe4f6.js → PopOver-BHpt5rsj.js} +5 -9
- flowfile/web/static/assets/{PopOver-d96599db.css → PopOver-CyYM4-rV.css} +1 -1
- flowfile/web/static/assets/{Read-90f366bc.css → Read-DJxkrTb_.css} +10 -10
- flowfile/web/static/assets/Read-TsLEFh3B.js +227 -0
- flowfile/web/static/assets/{RecordCount-e9048ccd.js → RecordCount-DkVixq9v.js} +18 -17
- flowfile/web/static/assets/{RecordId-ad02521d.js → RecordId-C2UEGlCf.js} +42 -39
- flowfile/web/static/assets/{SQLQueryComponent-2eeecf0b.js → SQLQueryComponent-Dr5KMoD3.js} +2 -3
- flowfile/web/static/assets/{Sample-9a68c23d.js → Sample-Cb3eQNmd.js} +30 -30
- flowfile/web/static/assets/{SecretSelector-2429f35a.js → SecretSelector-De2L2bSx.js} +3 -4
- flowfile/web/static/assets/{SecretsView-c6afc915.js → SecretsView-CheC9BPV.js} +13 -16
- flowfile/web/static/assets/{Select-fcd002b6.js → Select-CI8TloRs.js} +41 -36
- flowfile/web/static/assets/{SettingsSection-5ce15962.js → SettingsSection-B39ulIiI.js} +1 -2
- flowfile/web/static/assets/{SettingsSection-c6b1362c.js → SettingsSection-BiCc7S9h.js} +1 -2
- flowfile/web/static/assets/{SettingsSection-cebb91d5.js → SettingsSection-CITK_R7o.js} +2 -3
- flowfile/web/static/assets/{SettingsSection-26fe48d4.css → SettingsSection-D2GgY-Aq.css} +4 -4
- flowfile/web/static/assets/{SetupView-2d12e01f.js → SetupView-C1aXRDvp.js} +1 -2
- flowfile/web/static/assets/{SingleSelect-b67de4eb.js → SingleSelect-Kr_hz90m.js} +2 -2
- flowfile/web/static/assets/{SingleSelect.vue_vue_type_script_setup_true_lang-eedb70eb.js → SingleSelect.vue_vue_type_script_setup_true_lang-Rxht5Z5N.js} +1 -1
- flowfile/web/static/assets/{SliderInput-fd8134ac.js → SliderInput-CLqpCxCb.js} +1 -2
- flowfile/web/static/assets/{GroupBy-5792782d.css → Sort-BIt2kc_p.css} +1 -1
- flowfile/web/static/assets/{Sort-c005a573.js → Sort-Dnw_J6Qi.js} +25 -25
- flowfile/web/static/assets/{TextInput-1bb31dab.js → TextInput-wdlunIZC.js} +2 -2
- flowfile/web/static/assets/{TextInput.vue_vue_type_script_setup_true_lang-a51fe730.js → TextInput.vue_vue_type_script_setup_true_lang-Bcj3ywzv.js} +1 -1
- flowfile/web/static/assets/{TextToRows-4f363753.js → TextToRows-BhtyGWPq.js} +42 -49
- flowfile/web/static/assets/{TextToRows-12afb4f4.css → TextToRows-DivDOLDx.css} +9 -9
- flowfile/web/static/assets/{ToggleSwitch-ca0f2e5e.js → ToggleSwitch-B-6WzfFf.js} +2 -2
- flowfile/web/static/assets/{ToggleSwitch.vue_vue_type_script_setup_true_lang-49aa41d8.js → ToggleSwitch.vue_vue_type_script_setup_true_lang-Cj8LqT-b.js} +1 -1
- flowfile/web/static/assets/{UnavailableFields-f6147968.js → UnavailableFields-Yf6XSqFB.js} +2 -3
- flowfile/web/static/assets/{Union-c65f17b7.js → Union-CwpjeKYC.js} +20 -23
- flowfile/web/static/assets/{Unpivot-b6ad6427.css → Union-DQJcpp3-.css} +6 -6
- flowfile/web/static/assets/{Unique-a1d96fb2.js → Unique-25v3urqH.js} +75 -74
- flowfile/web/static/assets/{Union-d6a8d7d5.css → Unpivot-Deqh1gtI.css} +6 -6
- flowfile/web/static/assets/{Unpivot-c2657ff3.js → Unpivot-sYcTTXrq.js} +34 -27
- flowfile/web/static/assets/{UnpivotValidation-28e29a3b.js → UnpivotValidation-C5DDEKY2.js} +5 -7
- flowfile/web/static/assets/VueGraphicWalker-B8l1_Z92.js +131 -0
- flowfile/web/static/assets/VueGraphicWalker-Da_1-3me.css +21 -0
- flowfile/web/static/assets/{api-df48ec50.js → api-C0LvF-0C.js} +1 -1
- flowfile/web/static/assets/{api-ee542cf7.js → api-DaC83EO_.js} +1 -1
- flowfile/web/static/assets/client-C8Ygr6Gb.js +42 -0
- flowfile/web/static/assets/{dropDown-7576a76a.js → dropDown-D5YXaPRR.js} +7 -12
- flowfile/web/static/assets/{fullEditor-7583bef5.js → fullEditor-BVYnWm05.js} +300 -18
- flowfile/web/static/assets/genericNodeSettings-2wAu-QKn.css +75 -0
- flowfile/web/static/assets/genericNodeSettings-BBtW_Cpz.js +590 -0
- flowfile/web/static/assets/{VueGraphicWalker-2fc3ddd4.js → graphic-walker.es-VrK6vdGE.js} +92305 -89751
- flowfile/web/static/assets/index-BCJxPfM5.js +6693 -0
- flowfile/web/static/assets/{index-057d770d.js → index-CHPMUR0d.js} +150 -170
- flowfile/web/static/assets/index-DPkoZWq8.js +32 -0
- flowfile/web/static/assets/index-DnW_KC_I.js +277 -0
- flowfile/web/static/assets/index-UFXyfirV.css +10797 -0
- flowfile/web/static/assets/index-bcuE0Z0p.js +87456 -0
- flowfile/web/static/assets/{node.types-2c15bb7e.js → node.types-Dl4gtSW9.js} +2 -2
- flowfile/web/static/assets/{outputCsv-c492b15e.js → outputCsv-BELuBiJZ.js} +1 -2
- flowfile/web/static/assets/outputCsv-CdGkv-fN.css +2581 -0
- flowfile/web/static/assets/{outputExcel-13bfa10f.js → outputExcel-D0TTNM79.js} +1 -2
- flowfile/web/static/assets/{outputParquet-9be1523a.js → outputParquet-Cz9EbRHj.js} +1 -2
- flowfile/web/static/assets/{readCsv-5a49a8c9.js → readCsv-7bd3kUMI.js} +1 -2
- flowfile/web/static/assets/{readExcel-27c30ad8.js → readExcel-Cq8CCwIv.js} +3 -4
- flowfile/web/static/assets/{readParquet-c5244ad5.css → readParquet-CRDmBrsp.css} +4 -4
- flowfile/web/static/assets/{readParquet-446bde68.js → readParquet-DjR4mRaj.js} +4 -5
- flowfile/web/static/assets/{secrets.api-34431884.js → secrets.api-C9o2KE5V.js} +1 -1
- flowfile/web/static/assets/{selectDynamic-5754a2b1.js → selectDynamic-Bl5FVsME.js} +5 -7
- flowfile/web/static/assets/useNodeSettings-dMS9zmh_.js +69 -0
- flowfile/web/static/assets/{vue-codemirror.esm-8f46fb36.js → vue-codemirror.esm-CwaYwln0.js} +3469 -3064
- flowfile/web/static/assets/{vue-content-loader.es-808fe33a.js → vue-content-loader.es-CMoRXo7N.js} +3 -3
- flowfile/web/static/index.html +2 -3
- {flowfile-0.5.6.dist-info → flowfile-0.6.1.dist-info}/METADATA +2 -1
- flowfile-0.6.1.dist-info/RECORD +417 -0
- {flowfile-0.5.6.dist-info → flowfile-0.6.1.dist-info}/WHEEL +1 -1
- flowfile_core/auth/password.py +1 -0
- flowfile_core/database/init_db.py +7 -5
- flowfile_core/fileExplorer/funcs.py +2 -2
- flowfile_core/flowfile/code_generator/code_generator.py +13 -11
- flowfile_core/flowfile/filter_expressions.py +327 -0
- flowfile_core/flowfile/flow_data_engine/flow_data_engine.py +61 -59
- flowfile_core/flowfile/flow_data_engine/flow_file_column/type_registry.py +3 -29
- flowfile_core/flowfile/flow_data_engine/flow_file_column/utils.py +45 -14
- flowfile_core/flowfile/flow_data_engine/subprocess_operations/models.py +20 -3
- flowfile_core/flowfile/flow_data_engine/subprocess_operations/streaming.py +206 -0
- flowfile_core/flowfile/flow_data_engine/subprocess_operations/subprocess_operations.py +146 -24
- flowfile_core/flowfile/flow_graph.py +504 -190
- flowfile_core/flowfile/flow_node/__init__.py +32 -0
- flowfile_core/flowfile/flow_node/executor.py +404 -0
- flowfile_core/flowfile/flow_node/flow_node.py +207 -106
- flowfile_core/flowfile/flow_node/models.py +40 -0
- flowfile_core/flowfile/flow_node/output_field_config_applier.py +217 -0
- flowfile_core/flowfile/flow_node/schema_utils.py +78 -0
- flowfile_core/flowfile/flow_node/state.py +155 -0
- flowfile_core/flowfile/history_manager.py +401 -0
- flowfile_core/flowfile/manage/compatibility_enhancements.py +9 -0
- flowfile_core/flowfile/manage/io_flowfile.py +3 -1
- flowfile_core/flowfile/sources/external_sources/sql_source/models.py +20 -4
- flowfile_core/flowfile/util/execution_orderer.py +89 -36
- flowfile_core/routes/auth.py +8 -9
- flowfile_core/routes/routes.py +320 -101
- flowfile_core/routes/user_defined_components.py +18 -16
- flowfile_core/schemas/history_schema.py +220 -0
- flowfile_core/schemas/input_schema.py +130 -6
- flowfile_core/schemas/schemas.py +9 -0
- flowfile_core/schemas/transform_schema.py +27 -5
- flowfile_core/schemas/yaml_types.py +23 -5
- flowfile_frame/adding_expr.py +18 -126
- flowfile_frame/callable_utils.py +261 -0
- flowfile_frame/database/connection_manager.py +0 -1
- flowfile_frame/expr.py +8 -4
- flowfile_frame/flow_frame.py +41 -41
- flowfile_frame/lazy.py +3 -12
- flowfile_frame/lazy_methods.py +5 -64
- flowfile_frame/utils.py +13 -32
- flowfile_worker/funcs.py +6 -4
- flowfile_worker/main.py +2 -0
- flowfile_worker/models.py +31 -11
- flowfile_worker/routes.py +60 -35
- flowfile_worker/spawner.py +7 -1
- flowfile_worker/streaming.py +335 -0
- flowfile/web/static/assets/ContextMenu-366bf1b4.js +0 -9
- flowfile/web/static/assets/ContextMenu-85cf5b44.js +0 -9
- flowfile/web/static/assets/ContextMenu-9d28ae6d.js +0 -9
- flowfile/web/static/assets/Join-28b5e18f.css +0 -109
- flowfile/web/static/assets/Read-39b63932.js +0 -222
- flowfile/web/static/assets/VueGraphicWalker-430f0b86.css +0 -6
- flowfile/web/static/assets/database_reader-ce1e55f3.svg +0 -24
- flowfile/web/static/assets/database_writer-b4ad0753.svg +0 -23
- flowfile/web/static/assets/element-icons-9c88a535.woff +0 -0
- flowfile/web/static/assets/element-icons-de5eb258.ttf +0 -0
- flowfile/web/static/assets/genericNodeSettings-0155288b.js +0 -136
- flowfile/web/static/assets/genericNodeSettings-3b2507ea.css +0 -46
- flowfile/web/static/assets/index-aeec439d.js +0 -38
- flowfile/web/static/assets/index-ca6799de.js +0 -62760
- flowfile/web/static/assets/index-d60c9dd4.css +0 -10777
- flowfile/web/static/assets/nodeInput-d478b9ac.js +0 -2
- flowfile/web/static/assets/outputCsv-cc84e09f.css +0 -2499
- flowfile-0.5.6.dist-info/RECORD +0 -407
- /flowfile/web/static/assets/{AdminView-f53bad23.css → AdminView-B2Dthl3u.css} +0 -0
- /flowfile/web/static/assets/{CloudConnectionView-cf85f943.css → CloudConnectionView-BdFYGWV7.css} +0 -0
- /flowfile/web/static/assets/{ColumnActionInput-c44b7aee.css → ColumnActionInput-dCasSIC9.css} +0 -0
- /flowfile/web/static/assets/{ColumnSelector-371637fb.css → ColumnSelector-j6sEOjo1.css} +0 -0
- /flowfile/web/static/assets/{CustomNode-edb9b939.css → CustomNode-VPlajG0j.css} +0 -0
- /flowfile/web/static/assets/{DatabaseConnectionSettings-c20a1e16.css → DatabaseConnectionSettings-B78hXYgu.css} +0 -0
- /flowfile/web/static/assets/{DatabaseView-6655afd6.css → DatabaseView-B-_adk1s.css} +0 -0
- /flowfile/web/static/assets/{DocumentationView-9ea6e871.css → DocumentationView-CL7iipFL.css} +0 -0
- /flowfile/web/static/assets/{ExploreData-10c5acc8.css → ExploreData-DHjv0Plr.css} +0 -0
- /flowfile/web/static/assets/{LoginView-d325d632.css → LoginView-DN1BXY3e.css} +0 -0
- /flowfile/web/static/assets/{PivotValidation-0e905b1a.css → PivotValidation-DK-FARWe.css} +0 -0
- /flowfile/web/static/assets/{PivotValidation-41b57ad6.css → PivotValidation-FUa9F47u.css} +0 -0
- /flowfile/web/static/assets/{PolarsCode-2b1f1f23.css → PolarsCode-G-gRSrSc.css} +0 -0
- /flowfile/web/static/assets/{SQLQueryComponent-edb90b98.css → SQLQueryComponent-oAbWw0r-.css} +0 -0
- /flowfile/web/static/assets/{SecretSelector-6329f743.css → SecretSelector-CJSadIZx.css} +0 -0
- /flowfile/web/static/assets/{SecretsView-aa291340.css → SecretsView-DbzIRAba.css} +0 -0
- /flowfile/web/static/assets/{SettingsSection-8f980839.css → SettingsSection-BGcJnH6q.css} +0 -0
- /flowfile/web/static/assets/{SettingsSection-07fbbc39.css → SettingsSection-DDWn_EGW.css} +0 -0
- /flowfile/web/static/assets/{SetupView-ec26f76a.css → SetupView-CI1nd-5Z.css} +0 -0
- /flowfile/web/static/assets/{SliderInput-f2e4f23c.css → SliderInput-BRk-q_Dk.css} +0 -0
- /flowfile/web/static/assets/{UnavailableFields-394a1f78.css → UnavailableFields-DRKDImKe.css} +0 -0
- /flowfile/web/static/assets/{Unique-2b705521.css → Unique-Absb0aON.css} +0 -0
- /flowfile/web/static/assets/{UnpivotValidation-d5ca3b7b.css → UnpivotValidation-DSBkFgS-.css} +0 -0
- /flowfile/web/static/assets/{airbyte-292aa232.png → airbyte-W0xvIXwZ.png} +0 -0
- /flowfile/web/static/assets/{cloud_storage_reader-aa1415d6.png → cloud_storage_reader-3GpSCk90.png} +0 -0
- /flowfile/web/static/assets/{cross_join-d30c0290.png → cross_join-B0qpgYoV.png} +0 -0
- /flowfile/web/static/assets/{dropDown-1d6acbd9.css → dropDown-CE0VF5_P.css} +0 -0
- /flowfile/web/static/assets/{explore_data-8a0a2861.png → explore_data-tX6olPPL.png} +0 -0
- /flowfile/web/static/assets/{fa-brands-400-808443ae.ttf → fa-brands-400-D1LuMI3I.ttf} +0 -0
- /flowfile/web/static/assets/{fa-brands-400-d7236a19.woff2 → fa-brands-400-D_cYUPeE.woff2} +0 -0
- /flowfile/web/static/assets/{fa-regular-400-e3456d12.woff2 → fa-regular-400-BjRzuEpd.woff2} +0 -0
- /flowfile/web/static/assets/{fa-regular-400-54cf6086.ttf → fa-regular-400-DZaxPHgR.ttf} +0 -0
- /flowfile/web/static/assets/{fa-solid-900-aa759986.woff2 → fa-solid-900-CTAAxXor.woff2} +0 -0
- /flowfile/web/static/assets/{fa-solid-900-d2f05935.ttf → fa-solid-900-D0aA9rwL.ttf} +0 -0
- /flowfile/web/static/assets/{fa-v4compatibility-0ce9033c.woff2 → fa-v4compatibility-C9RhG_FT.woff2} +0 -0
- /flowfile/web/static/assets/{fa-v4compatibility-30f6abf6.ttf → fa-v4compatibility-CCth-dXg.ttf} +0 -0
- /flowfile/web/static/assets/{filter-d7708bda.png → filter-WRdZyUOw.png} +0 -0
- /flowfile/web/static/assets/{formula-eeeb1611.png → formula-CgM7uHVI.png} +0 -0
- /flowfile/web/static/assets/{fullEditor-fe9f7e18.css → fullEditor-CmDI7T9F.css} +0 -0
- /flowfile/web/static/assets/{fuzzy_match-40c161b2.png → fuzzy_match-Yon3k5Tc.png} +0 -0
- /flowfile/web/static/assets/{graph_solver-8b7888b8.png → graph_solver-BlMrBttD.png} +0 -0
- /flowfile/web/static/assets/{group_by-80561fc3.png → group_by-Gici0CSS.png} +0 -0
- /flowfile/web/static/assets/{input_data-ab2eb678.png → input_data-BRdGecLc.png} +0 -0
- /flowfile/web/static/assets/{join-349043ae.png → join-BITWRu73.png} +0 -0
- /flowfile/web/static/assets/{manual_input-ae98f31d.png → manual_input-CFvo_EUS.png} +0 -0
- /flowfile/web/static/assets/{old_join-5d0eb604.png → old_join-B9bkpPqv.png} +0 -0
- /flowfile/web/static/assets/{output-06ec0371.png → output-Dp7-ZpC4.png} +0 -0
- /flowfile/web/static/assets/{outputExcel-f5d272b2.css → outputExcel-CKgRe2iT.css} +0 -0
- /flowfile/web/static/assets/{outputParquet-54597c3c.css → outputParquet-d7j407cK.css} +0 -0
- /flowfile/web/static/assets/{pivot-9660df51.png → pivot-DSxKhNlD.png} +0 -0
- /flowfile/web/static/assets/{polars_code-05ce5dc6.png → polars_code-DxiztZ1c.png} +0 -0
- /flowfile/web/static/assets/{readCsv-3bfac4c3.css → readCsv-BG-1Jilp.css} +0 -0
- /flowfile/web/static/assets/{readExcel-3db6b763.css → readExcel-DBQXKPtC.css} +0 -0
- /flowfile/web/static/assets/{record_count-dab44eb5.png → record_count-DCeaLtpS.png} +0 -0
- /flowfile/web/static/assets/{record_id-0b15856b.png → record_id-FeUjyIFh.png} +0 -0
- /flowfile/web/static/assets/{sample-693a88b5.png → sample-DeqfRiB-.png} +0 -0
- /flowfile/web/static/assets/{select-b0d0437a.png → select-D4JjbdjS.png} +0 -0
- /flowfile/web/static/assets/{selectDynamic-f2fb394f.css → selectDynamic-CjeTPUUo.css} +0 -0
- /flowfile/web/static/assets/{sort-2aa579f0.png → sort-DGwUG9WS.png} +0 -0
- /flowfile/web/static/assets/{summarize-2a099231.png → summarize-DFaNHpfp.png} +0 -0
- /flowfile/web/static/assets/{text_to_rows-859b29ea.png → text_to_rows-BdiAewrN.png} +0 -0
- /flowfile/web/static/assets/{union-2d8609f4.png → union-DCK-LSMq.png} +0 -0
- /flowfile/web/static/assets/{unique-1958b98a.png → unique-CdP3zZIq.png} +0 -0
- /flowfile/web/static/assets/{unpivot-d3cb4b5b.png → unpivot-CHttrEt8.png} +0 -0
- /flowfile/web/static/assets/{user-defined-icon-0ae16c90.png → user-defined-icon-BcIp2Vzo.png} +0 -0
- /flowfile/web/static/assets/{view-7a0f0be1.png → view-DUSRwjvq.png} +0 -0
- {flowfile-0.5.6.dist-info → flowfile-0.6.1.dist-info}/entry_points.txt +0 -0
- {flowfile-0.5.6.dist-info → flowfile-0.6.1.dist-info}/licenses/LICENSE +0 -0
flowfile_core/routes/auth.py
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
# app_routes/auth.py
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
-
from typing import Optional, List
|
|
5
4
|
|
|
6
|
-
from fastapi import APIRouter, Depends,
|
|
5
|
+
from fastapi import APIRouter, Depends, Form, HTTPException, Request, status
|
|
7
6
|
from sqlalchemy.orm import Session
|
|
8
7
|
|
|
9
|
-
from flowfile_core.auth.jwt import get_current_active_user, get_current_admin_user
|
|
10
|
-
from flowfile_core.auth.models import Token, User, UserCreate, UserUpdate
|
|
11
|
-
from flowfile_core.auth.password import
|
|
12
|
-
from flowfile_core.database.connection import get_db
|
|
8
|
+
from flowfile_core.auth.jwt import create_access_token, get_current_active_user, get_current_admin_user
|
|
9
|
+
from flowfile_core.auth.models import ChangePassword, Token, User, UserCreate, UserUpdate
|
|
10
|
+
from flowfile_core.auth.password import PASSWORD_REQUIREMENTS, get_password_hash, validate_password, verify_password
|
|
13
11
|
from flowfile_core.database import models as db_models
|
|
12
|
+
from flowfile_core.database.connection import get_db
|
|
14
13
|
|
|
15
14
|
router = APIRouter()
|
|
16
15
|
|
|
@@ -19,8 +18,8 @@ router = APIRouter()
|
|
|
19
18
|
async def login_for_access_token(
|
|
20
19
|
request: Request,
|
|
21
20
|
db: Session = Depends(get_db),
|
|
22
|
-
username:
|
|
23
|
-
password:
|
|
21
|
+
username: str | None = Form(None),
|
|
22
|
+
password: str | None = Form(None)
|
|
24
23
|
):
|
|
25
24
|
# In Electron mode, auto-authenticate without requiring form data
|
|
26
25
|
if os.environ.get("FLOWFILE_MODE") == "electron":
|
|
@@ -58,7 +57,7 @@ async def read_users_me(current_user=Depends(get_current_active_user)):
|
|
|
58
57
|
|
|
59
58
|
# ============= Admin User Management Endpoints =============
|
|
60
59
|
|
|
61
|
-
@router.get("/users", response_model=
|
|
60
|
+
@router.get("/users", response_model=list[User])
|
|
62
61
|
async def list_users(
|
|
63
62
|
current_user: User = Depends(get_current_admin_user),
|
|
64
63
|
db: Session = Depends(get_db)
|
flowfile_core/routes/routes.py
CHANGED
|
@@ -11,53 +11,51 @@ import inspect
|
|
|
11
11
|
import logging
|
|
12
12
|
import os
|
|
13
13
|
from pathlib import Path
|
|
14
|
-
from typing import
|
|
14
|
+
from typing import Any
|
|
15
15
|
|
|
16
|
-
from fastapi import APIRouter,
|
|
16
|
+
from fastapi import APIRouter, BackgroundTasks, Body, Depends, File, HTTPException, UploadFile, status
|
|
17
17
|
from fastapi.responses import JSONResponse, Response
|
|
18
|
+
|
|
18
19
|
# External dependencies
|
|
19
20
|
from polars_expr_transformer.function_overview import get_all_expressions, get_expression_overview
|
|
20
21
|
from sqlalchemy.orm import Session
|
|
21
22
|
|
|
22
23
|
from flowfile_core import flow_file_handler
|
|
24
|
+
|
|
23
25
|
# Core modules
|
|
24
26
|
from flowfile_core.auth.jwt import get_current_active_user
|
|
25
27
|
from flowfile_core.configs import logger
|
|
26
|
-
from flowfile_core.configs.node_store import
|
|
28
|
+
from flowfile_core.configs.node_store import check_if_has_default_setting, nodes_list
|
|
27
29
|
from flowfile_core.database.connection import get_db
|
|
30
|
+
|
|
28
31
|
# File handling
|
|
29
32
|
from flowfile_core.fileExplorer.funcs import (
|
|
30
|
-
SecureFileExplorer,
|
|
31
33
|
FileInfo,
|
|
34
|
+
SecureFileExplorer,
|
|
32
35
|
get_files_from_directory,
|
|
33
|
-
validate_file_path,
|
|
34
36
|
validate_path_under_cwd,
|
|
35
37
|
)
|
|
36
38
|
from flowfile_core.flowfile.analytics.analytics_processor import AnalyticsProcessor
|
|
37
39
|
from flowfile_core.flowfile.code_generator.code_generator import export_flow_to_polars
|
|
38
|
-
from flowfile_core.flowfile.database_connection_manager.db_connections import (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
from flowfile_core.flowfile.database_connection_manager.db_connections import (
|
|
41
|
+
delete_database_connection,
|
|
42
|
+
get_all_database_connections_interface,
|
|
43
|
+
get_database_connection,
|
|
44
|
+
store_database_connection,
|
|
45
|
+
)
|
|
42
46
|
from flowfile_core.flowfile.extensions import get_instant_func_results
|
|
43
47
|
from flowfile_core.flowfile.flow_graph import add_connection, delete_connection
|
|
44
48
|
from flowfile_core.flowfile.sources.external_sources.sql_source.sql_source import create_sql_source_from_db_settings
|
|
45
49
|
from flowfile_core.run_lock import get_flow_run_lock
|
|
46
|
-
from flowfile_core.schemas import input_schema,
|
|
50
|
+
from flowfile_core.schemas import input_schema, output_model, schemas
|
|
51
|
+
from flowfile_core.schemas.history_schema import HistoryActionType, HistoryState, OperationResponse, UndoRedoResult
|
|
47
52
|
from flowfile_core.utils import excel_file_manager
|
|
48
53
|
from flowfile_core.utils.fileManager import create_dir
|
|
49
54
|
from flowfile_core.utils.utils import camel_case_to_snake_case
|
|
50
55
|
from shared.storage_config import storage
|
|
51
56
|
|
|
52
|
-
|
|
53
57
|
router = APIRouter(dependencies=[Depends(get_current_active_user)])
|
|
54
58
|
|
|
55
|
-
# Initialize services
|
|
56
|
-
file_explorer = SecureFileExplorer(
|
|
57
|
-
start_path=storage.user_data_directory,
|
|
58
|
-
sandbox_root=storage.user_data_directory
|
|
59
|
-
)
|
|
60
|
-
|
|
61
59
|
|
|
62
60
|
def get_node_model(setting_name_ref: str):
|
|
63
61
|
"""(Internal) Retrieves a node's Pydantic model from the input_schema module by its name."""
|
|
@@ -119,44 +117,16 @@ async def get_local_files(directory: str) -> list[FileInfo]:
|
|
|
119
117
|
return files
|
|
120
118
|
|
|
121
119
|
|
|
122
|
-
@router.get('/files/
|
|
123
|
-
async def
|
|
124
|
-
"""
|
|
125
|
-
|
|
126
|
-
return f
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
@router.post('/files/navigate_up/', response_model=str, tags=['file manager'])
|
|
130
|
-
async def navigate_up() -> str:
|
|
131
|
-
"""Navigates the file explorer one directory level up."""
|
|
132
|
-
file_explorer.navigate_up()
|
|
133
|
-
return str(file_explorer.current_path)
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
@router.post('/files/navigate_into/', response_model=str, tags=['file manager'])
|
|
137
|
-
async def navigate_into_directory(directory_name: str) -> str:
|
|
138
|
-
"""Navigates the file explorer into a specified subdirectory."""
|
|
139
|
-
file_explorer.navigate_into(directory_name)
|
|
140
|
-
return str(file_explorer.current_path)
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
@router.post('/files/navigate_to/', tags=['file manager'])
|
|
144
|
-
async def navigate_to_directory(directory_name: str) -> str:
|
|
145
|
-
"""Navigates the file explorer to an absolute directory path."""
|
|
146
|
-
file_explorer.navigate_to(directory_name)
|
|
147
|
-
return str(file_explorer.current_path)
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
@router.get('/files/current_path/', response_model=str, tags=['file manager'])
|
|
151
|
-
async def get_current_path() -> str:
|
|
152
|
-
"""Returns the current absolute path of the file explorer."""
|
|
153
|
-
return str(file_explorer.current_path)
|
|
120
|
+
@router.get('/files/default_path/', response_model=str, tags=['file manager'])
|
|
121
|
+
async def get_default_path() -> str:
|
|
122
|
+
"""Returns the default starting path for the file browser (user data directory)."""
|
|
123
|
+
return str(storage.user_data_directory)
|
|
154
124
|
|
|
155
125
|
|
|
156
|
-
@router.get('/files/directory_contents/', response_model=
|
|
157
|
-
async def get_directory_contents(directory: str, file_types:
|
|
158
|
-
include_hidden: bool = False) ->
|
|
159
|
-
"""Gets the contents of
|
|
126
|
+
@router.get('/files/directory_contents/', response_model=list[FileInfo], tags=['file manager'])
|
|
127
|
+
async def get_directory_contents(directory: str, file_types: list[str] = None,
|
|
128
|
+
include_hidden: bool = False) -> list[FileInfo]:
|
|
129
|
+
"""Gets the contents of a directory path.
|
|
160
130
|
|
|
161
131
|
Args:
|
|
162
132
|
directory: The absolute path to the directory.
|
|
@@ -174,12 +144,6 @@ async def get_directory_contents(directory: str, file_types: List[str] = None,
|
|
|
174
144
|
HTTPException(404, 'Could not access the directory')
|
|
175
145
|
|
|
176
146
|
|
|
177
|
-
@router.get('/files/current_directory_contents/', response_model=List[FileInfo], tags=['file manager'])
|
|
178
|
-
async def get_current_directory_contents(file_types: List[str] = None, include_hidden: bool = False) -> List[FileInfo]:
|
|
179
|
-
"""Gets the contents of the file explorer's current directory."""
|
|
180
|
-
return file_explorer.list_contents(file_types=file_types, show_hidden=include_hidden)
|
|
181
|
-
|
|
182
|
-
|
|
183
147
|
@router.post('/files/create_directory', response_model=output_model.OutputDir, tags=['file manager'])
|
|
184
148
|
def create_directory(new_directory: input_schema.NewDirectory) -> bool:
|
|
185
149
|
"""Creates a new directory at the specified path.
|
|
@@ -273,6 +237,10 @@ def apply_standard_layout(flow_id: int):
|
|
|
273
237
|
raise HTTPException(status_code=404, detail="Flow not found")
|
|
274
238
|
if flow.flow_settings.is_running:
|
|
275
239
|
raise HTTPException(422, "Flow is running")
|
|
240
|
+
|
|
241
|
+
# Capture history BEFORE the layout change
|
|
242
|
+
flow.capture_history_snapshot(HistoryActionType.APPLY_LAYOUT, "Apply standard layout")
|
|
243
|
+
|
|
276
244
|
flow.apply_layout()
|
|
277
245
|
|
|
278
246
|
|
|
@@ -309,14 +277,17 @@ def add_flow_input(input_data: input_schema.NodeDatasource):
|
|
|
309
277
|
flow.add_datasource(input_data)
|
|
310
278
|
|
|
311
279
|
|
|
312
|
-
@router.post('/editor/copy_node', tags=['editor'])
|
|
313
|
-
def copy_node(node_id_to_copy_from: int, flow_id_to_copy_from: int, node_promise: input_schema.NodePromise):
|
|
280
|
+
@router.post('/editor/copy_node', tags=['editor'], response_model=OperationResponse)
|
|
281
|
+
def copy_node(node_id_to_copy_from: int, flow_id_to_copy_from: int, node_promise: input_schema.NodePromise) -> OperationResponse:
|
|
314
282
|
"""Copies an existing node's settings to a new node promise.
|
|
315
283
|
|
|
316
284
|
Args:
|
|
317
285
|
node_id_to_copy_from: The ID of the node to copy the settings from.
|
|
318
286
|
flow_id_to_copy_from: The ID of the flow containing the source node.
|
|
319
287
|
node_promise: A `NodePromise` representing the new node to be created.
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
OperationResponse with current history state.
|
|
320
291
|
"""
|
|
321
292
|
try:
|
|
322
293
|
flow_to_copy_from = flow_file_handler.get_flow(flow_id_to_copy_from)
|
|
@@ -330,22 +301,32 @@ def copy_node(node_id_to_copy_from: int, flow_id_to_copy_from: int, node_promise
|
|
|
330
301
|
if flow.flow_settings.is_running:
|
|
331
302
|
raise HTTPException(422, "Flow is running")
|
|
332
303
|
|
|
304
|
+
# Capture history BEFORE the change
|
|
305
|
+
flow.capture_history_snapshot(
|
|
306
|
+
HistoryActionType.COPY_NODE,
|
|
307
|
+
f"Copy {node_promise.node_type} node",
|
|
308
|
+
node_id=node_promise.node_id
|
|
309
|
+
)
|
|
310
|
+
|
|
333
311
|
if flow.get_node(node_promise.node_id) is not None:
|
|
334
312
|
flow.delete_node(node_promise.node_id)
|
|
335
313
|
|
|
336
314
|
if node_promise.node_type == "explore_data":
|
|
337
315
|
flow.add_initial_node_analysis(node_promise)
|
|
338
|
-
return
|
|
316
|
+
return OperationResponse(success=True, history=flow.get_history_state())
|
|
339
317
|
|
|
340
318
|
flow.copy_node(node_promise, node_to_copy.setting_input, node_to_copy.node_type)
|
|
341
319
|
|
|
320
|
+
return OperationResponse(success=True, history=flow.get_history_state())
|
|
321
|
+
|
|
342
322
|
except Exception as e:
|
|
343
323
|
logger.error(e)
|
|
344
324
|
raise HTTPException(422, str(e))
|
|
345
325
|
|
|
346
326
|
|
|
347
|
-
@router.post('/editor/add_node/', tags=['editor'])
|
|
348
|
-
def add_node(flow_id: int, node_id: int, node_type: str, pos_x: int
|
|
327
|
+
@router.post('/editor/add_node/', tags=['editor'], response_model=OperationResponse)
|
|
328
|
+
def add_node(flow_id: int, node_id: int, node_type: str, pos_x: int | float = 0,
|
|
329
|
+
pos_y: int | float = 0) -> OperationResponse | None:
|
|
349
330
|
"""Adds a new, unconfigured node (a "promise") to the flow graph.
|
|
350
331
|
|
|
351
332
|
Args:
|
|
@@ -354,11 +335,19 @@ def add_node(flow_id: int, node_id: int, node_type: str, pos_x: int = 0, pos_y:
|
|
|
354
335
|
node_type: The type of the node to add (e.g., 'filter', 'join').
|
|
355
336
|
pos_x: The X coordinate for the node's position in the UI.
|
|
356
337
|
pos_y: The Y coordinate for the node's position in the UI.
|
|
338
|
+
|
|
339
|
+
Returns:
|
|
340
|
+
OperationResponse with current history state.
|
|
357
341
|
"""
|
|
342
|
+
if isinstance(pos_x, float):
|
|
343
|
+
pos_x = int(pos_x)
|
|
344
|
+
if isinstance(pos_y, float):
|
|
345
|
+
pos_y = int(pos_y)
|
|
358
346
|
flow = flow_file_handler.get_flow(flow_id)
|
|
359
347
|
logger.info(f'Adding a promise for {node_type}')
|
|
360
348
|
if flow.flow_settings.is_running:
|
|
361
349
|
raise HTTPException(422, 'Flow is running')
|
|
350
|
+
|
|
362
351
|
node = flow.get_node(node_id)
|
|
363
352
|
if node is not None:
|
|
364
353
|
flow.delete_node(node_id)
|
|
@@ -367,42 +356,94 @@ def add_node(flow_id: int, node_id: int, node_type: str, pos_x: int = 0, pos_y:
|
|
|
367
356
|
node_type=node_type)
|
|
368
357
|
if node_type == 'explore_data':
|
|
369
358
|
flow.add_initial_node_analysis(node_promise)
|
|
370
|
-
return
|
|
371
359
|
else:
|
|
372
|
-
|
|
373
|
-
flow.
|
|
374
|
-
|
|
375
|
-
if check_if_has_default_setting(node_type):
|
|
376
|
-
logger.info(f'Found standard settings for {node_type}, trying to upload them')
|
|
377
|
-
setting_name_ref = 'node' + node_type.replace('_', '')
|
|
378
|
-
node_model = get_node_model(setting_name_ref)
|
|
379
|
-
add_func = getattr(flow, 'add_' + node_type)
|
|
380
|
-
initial_settings = node_model(flow_id=flow_id, node_id=node_id, cache_results=False,
|
|
381
|
-
pos_x=pos_x, pos_y=pos_y, node_type=node_type)
|
|
382
|
-
add_func(initial_settings)
|
|
360
|
+
# Capture state BEFORE adding node (for batched history)
|
|
361
|
+
pre_snapshot = flow.get_flowfile_data() if flow.flow_settings.track_history else None
|
|
383
362
|
|
|
363
|
+
logger.info("Adding node")
|
|
364
|
+
# Add node without individual history tracking
|
|
365
|
+
flow.add_node_promise(node_promise, track_history=False)
|
|
366
|
+
|
|
367
|
+
if check_if_has_default_setting(node_type):
|
|
368
|
+
logger.info(f'Found standard settings for {node_type}, trying to upload them')
|
|
369
|
+
setting_name_ref = 'node' + node_type.replace('_', '')
|
|
370
|
+
node_model = get_node_model(setting_name_ref)
|
|
371
|
+
|
|
372
|
+
# Temporarily disable history tracking for initial settings
|
|
373
|
+
original_track_history = flow.flow_settings.track_history
|
|
374
|
+
flow.flow_settings.track_history = False
|
|
375
|
+
try:
|
|
376
|
+
add_func = getattr(flow, 'add_' + node_type)
|
|
377
|
+
initial_settings = node_model(flow_id=flow_id, node_id=node_id, cache_results=False,
|
|
378
|
+
pos_x=pos_x, pos_y=pos_y, node_type=node_type)
|
|
379
|
+
add_func(initial_settings)
|
|
380
|
+
finally:
|
|
381
|
+
flow.flow_settings.track_history = original_track_history
|
|
382
|
+
|
|
383
|
+
# Capture batched history entry for the whole add_node operation
|
|
384
|
+
if pre_snapshot is not None and flow.flow_settings.track_history:
|
|
385
|
+
from flowfile_core.schemas.history_schema import HistoryActionType
|
|
386
|
+
flow._history_manager.capture_if_changed(
|
|
387
|
+
flow,
|
|
388
|
+
pre_snapshot,
|
|
389
|
+
HistoryActionType.ADD_NODE,
|
|
390
|
+
f"Add {node_type} node",
|
|
391
|
+
node_id,
|
|
392
|
+
)
|
|
393
|
+
logger.info(f"History: Captured batched 'Add {node_type} node' entry")
|
|
394
|
+
|
|
395
|
+
logger.info(f"History state after add_node: {flow.get_history_state()}")
|
|
396
|
+
return OperationResponse(success=True, history=flow.get_history_state())
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
@router.post('/editor/delete_node/', tags=['editor'], response_model=OperationResponse)
|
|
400
|
+
def delete_node(flow_id: int | None, node_id: int) -> OperationResponse:
|
|
401
|
+
"""Deletes a node from the flow graph.
|
|
384
402
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
"""
|
|
403
|
+
Returns:
|
|
404
|
+
OperationResponse with current history state.
|
|
405
|
+
"""
|
|
388
406
|
logger.info('Deleting node')
|
|
389
407
|
flow = flow_file_handler.get_flow(flow_id)
|
|
390
408
|
if flow.flow_settings.is_running:
|
|
391
409
|
raise HTTPException(422, 'Flow is running')
|
|
410
|
+
|
|
411
|
+
# Capture history BEFORE the change
|
|
412
|
+
node = flow.get_node(node_id)
|
|
413
|
+
node_type = node.node_type if node else "unknown"
|
|
414
|
+
flow.capture_history_snapshot(HistoryActionType.DELETE_NODE, f"Delete {node_type} node", node_id=node_id)
|
|
415
|
+
|
|
392
416
|
flow.delete_node(node_id)
|
|
393
417
|
|
|
418
|
+
return OperationResponse(success=True, history=flow.get_history_state())
|
|
419
|
+
|
|
394
420
|
|
|
395
|
-
@router.post('/editor/delete_connection/', tags=['editor'])
|
|
396
|
-
def delete_node_connection(flow_id: int, node_connection: input_schema.NodeConnection = None):
|
|
397
|
-
"""Deletes a connection (edge) between two nodes.
|
|
421
|
+
@router.post('/editor/delete_connection/', tags=['editor'], response_model=OperationResponse)
|
|
422
|
+
def delete_node_connection(flow_id: int, node_connection: input_schema.NodeConnection = None) -> OperationResponse:
|
|
423
|
+
"""Deletes a connection (edge) between two nodes.
|
|
424
|
+
|
|
425
|
+
Returns:
|
|
426
|
+
OperationResponse with current history state.
|
|
427
|
+
"""
|
|
398
428
|
flow_id = int(flow_id)
|
|
399
429
|
logger.info(
|
|
400
430
|
f'Deleting connection node {node_connection.output_connection.node_id} to node {node_connection.input_connection.node_id}')
|
|
401
431
|
flow = flow_file_handler.get_flow(flow_id)
|
|
402
432
|
if flow.flow_settings.is_running:
|
|
403
433
|
raise HTTPException(422, 'Flow is running')
|
|
434
|
+
|
|
435
|
+
# Capture history BEFORE the change
|
|
436
|
+
from_id = node_connection.output_connection.node_id
|
|
437
|
+
to_id = node_connection.input_connection.node_id
|
|
438
|
+
flow.capture_history_snapshot(
|
|
439
|
+
HistoryActionType.DELETE_CONNECTION,
|
|
440
|
+
f"Delete connection {from_id} -> {to_id}"
|
|
441
|
+
)
|
|
442
|
+
|
|
404
443
|
delete_connection(flow, node_connection)
|
|
405
444
|
|
|
445
|
+
return OperationResponse(success=True, history=flow.get_history_state())
|
|
446
|
+
|
|
406
447
|
|
|
407
448
|
@router.post("/db_connection_lib", tags=['db_connections'])
|
|
408
449
|
def create_db_connection(input_connection: input_schema.FullDatabaseConnection,
|
|
@@ -436,34 +477,49 @@ def delete_db_connection(connection_name: str,
|
|
|
436
477
|
|
|
437
478
|
|
|
438
479
|
@router.get('/db_connection_lib', tags=['db_connections'],
|
|
439
|
-
response_model=
|
|
480
|
+
response_model=list[input_schema.FullDatabaseConnectionInterface])
|
|
440
481
|
def get_db_connections(
|
|
441
482
|
db: Session = Depends(get_db),
|
|
442
|
-
current_user=Depends(get_current_active_user)) ->
|
|
483
|
+
current_user=Depends(get_current_active_user)) -> list[input_schema.FullDatabaseConnectionInterface]:
|
|
443
484
|
"""Retrieves all stored database connections for the current user (without passwords)."""
|
|
444
485
|
return get_all_database_connections_interface(db, current_user.id)
|
|
445
486
|
|
|
446
487
|
|
|
447
|
-
@router.post('/editor/connect_node/', tags=['editor'])
|
|
448
|
-
def connect_node(flow_id: int, node_connection: input_schema.NodeConnection):
|
|
449
|
-
"""Creates a connection (edge) between two nodes in the flow graph.
|
|
488
|
+
@router.post('/editor/connect_node/', tags=['editor'], response_model=OperationResponse)
|
|
489
|
+
def connect_node(flow_id: int, node_connection: input_schema.NodeConnection) -> OperationResponse:
|
|
490
|
+
"""Creates a connection (edge) between two nodes in the flow graph.
|
|
491
|
+
|
|
492
|
+
Returns:
|
|
493
|
+
OperationResponse with current history state.
|
|
494
|
+
"""
|
|
450
495
|
flow = flow_file_handler.get_flow(flow_id)
|
|
451
496
|
if flow is None:
|
|
452
497
|
logger.info('could not find the flow')
|
|
453
498
|
raise HTTPException(404, 'could not find the flow')
|
|
454
499
|
if flow.flow_settings.is_running:
|
|
455
500
|
raise HTTPException(422, 'Flow is running')
|
|
501
|
+
|
|
502
|
+
# Capture history BEFORE the change
|
|
503
|
+
from_id = node_connection.output_connection.node_id
|
|
504
|
+
to_id = node_connection.input_connection.node_id
|
|
505
|
+
flow.capture_history_snapshot(
|
|
506
|
+
HistoryActionType.ADD_CONNECTION,
|
|
507
|
+
f"Connect {from_id} -> {to_id}"
|
|
508
|
+
)
|
|
509
|
+
|
|
456
510
|
add_connection(flow, node_connection)
|
|
457
511
|
|
|
512
|
+
return OperationResponse(success=True, history=flow.get_history_state())
|
|
513
|
+
|
|
458
514
|
|
|
459
|
-
@router.get('/editor/expression_doc', tags=['editor'], response_model=
|
|
460
|
-
def get_expression_doc() ->
|
|
515
|
+
@router.get('/editor/expression_doc', tags=['editor'], response_model=list[output_model.ExpressionsOverview])
|
|
516
|
+
def get_expression_doc() -> list[output_model.ExpressionsOverview]:
|
|
461
517
|
"""Retrieves documentation for available Polars expressions."""
|
|
462
518
|
return get_expression_overview()
|
|
463
519
|
|
|
464
520
|
|
|
465
|
-
@router.get('/editor/expressions', tags=['editor'], response_model=
|
|
466
|
-
def get_expressions() ->
|
|
521
|
+
@router.get('/editor/expressions', tags=['editor'], response_model=list[str])
|
|
522
|
+
def get_expressions() -> list[str]:
|
|
467
523
|
"""Retrieves a list of all available Flowfile expression names."""
|
|
468
524
|
return get_all_expressions()
|
|
469
525
|
|
|
@@ -517,17 +573,92 @@ def close_flow(flow_id: int, current_user=Depends(get_current_active_user)) -> N
|
|
|
517
573
|
flow_file_handler.delete_flow(flow_id, user_id=user_id)
|
|
518
574
|
|
|
519
575
|
|
|
520
|
-
|
|
521
|
-
|
|
576
|
+
# ==================== History/Undo-Redo Endpoints ====================
|
|
577
|
+
|
|
578
|
+
@router.post('/editor/undo/', tags=['editor'], response_model=UndoRedoResult)
|
|
579
|
+
def undo_action(flow_id: int) -> UndoRedoResult:
|
|
580
|
+
"""Undo the last action on the flow graph.
|
|
581
|
+
|
|
582
|
+
Args:
|
|
583
|
+
flow_id: The ID of the flow to undo.
|
|
584
|
+
|
|
585
|
+
Returns:
|
|
586
|
+
UndoRedoResult indicating success or failure.
|
|
587
|
+
"""
|
|
588
|
+
flow = flow_file_handler.get_flow(flow_id)
|
|
589
|
+
if flow is None:
|
|
590
|
+
raise HTTPException(404, 'Could not find the flow')
|
|
591
|
+
if flow.flow_settings.is_running:
|
|
592
|
+
raise HTTPException(422, 'Flow is running')
|
|
593
|
+
return flow.undo()
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
@router.post('/editor/redo/', tags=['editor'], response_model=UndoRedoResult)
|
|
597
|
+
def redo_action(flow_id: int) -> UndoRedoResult:
|
|
598
|
+
"""Redo the last undone action on the flow graph.
|
|
599
|
+
|
|
600
|
+
Args:
|
|
601
|
+
flow_id: The ID of the flow to redo.
|
|
602
|
+
|
|
603
|
+
Returns:
|
|
604
|
+
UndoRedoResult indicating success or failure.
|
|
605
|
+
"""
|
|
606
|
+
flow = flow_file_handler.get_flow(flow_id)
|
|
607
|
+
if flow is None:
|
|
608
|
+
raise HTTPException(404, 'Could not find the flow')
|
|
609
|
+
if flow.flow_settings.is_running:
|
|
610
|
+
raise HTTPException(422, 'Flow is running')
|
|
611
|
+
return flow.redo()
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
@router.get('/editor/history_status/', tags=['editor'], response_model=HistoryState)
|
|
615
|
+
def get_history_status(flow_id: int) -> HistoryState:
|
|
616
|
+
"""Get the current state of the history system for a flow.
|
|
617
|
+
|
|
618
|
+
Args:
|
|
619
|
+
flow_id: The ID of the flow to get history status for.
|
|
620
|
+
|
|
621
|
+
Returns:
|
|
622
|
+
HistoryState with information about available undo/redo operations.
|
|
623
|
+
"""
|
|
624
|
+
flow = flow_file_handler.get_flow(flow_id)
|
|
625
|
+
if flow is None:
|
|
626
|
+
raise HTTPException(404, 'Could not find the flow')
|
|
627
|
+
return flow.get_history_state()
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
@router.post('/editor/history_clear/', tags=['editor'])
|
|
631
|
+
def clear_history(flow_id: int):
|
|
632
|
+
"""Clear all history for a flow.
|
|
633
|
+
|
|
634
|
+
Args:
|
|
635
|
+
flow_id: The ID of the flow to clear history for.
|
|
636
|
+
"""
|
|
637
|
+
flow = flow_file_handler.get_flow(flow_id)
|
|
638
|
+
if flow is None:
|
|
639
|
+
raise HTTPException(404, 'Could not find the flow')
|
|
640
|
+
flow._history_manager.clear()
|
|
641
|
+
return {"message": "History cleared successfully"}
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
# ==================== End History Endpoints ====================
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
@router.post('/update_settings/', tags=['transform'], response_model=OperationResponse)
|
|
648
|
+
def add_generic_settings(input_data: dict[str, Any], node_type: str, current_user=Depends(get_current_active_user)) -> OperationResponse:
|
|
522
649
|
"""A generic endpoint to update the settings of any node.
|
|
523
650
|
|
|
524
651
|
This endpoint dynamically determines the correct Pydantic model and update
|
|
525
652
|
function based on the `node_type` parameter.
|
|
653
|
+
|
|
654
|
+
Returns:
|
|
655
|
+
OperationResponse with current history state.
|
|
526
656
|
"""
|
|
527
657
|
input_data['user_id'] = current_user.id
|
|
528
658
|
node_type = camel_case_to_snake_case(node_type)
|
|
529
659
|
flow_id = int(input_data.get('flow_id'))
|
|
530
|
-
|
|
660
|
+
node_id = int(input_data.get('node_id'))
|
|
661
|
+
logger.info(f'Updating the data for flow: {flow_id}, node {node_id}')
|
|
531
662
|
flow = flow_file_handler.get_flow(flow_id)
|
|
532
663
|
if flow.flow_settings.is_running:
|
|
533
664
|
raise HTTPException(422, 'Flow is running')
|
|
@@ -548,13 +679,16 @@ def add_generic_settings(input_data: Dict[str, Any], node_type: str, current_use
|
|
|
548
679
|
if parsed_input is None:
|
|
549
680
|
raise HTTPException(404, 'could not find the interface')
|
|
550
681
|
try:
|
|
682
|
+
# History capture is handled by the decorator on each add_* method
|
|
551
683
|
add_func(parsed_input)
|
|
552
684
|
except Exception as e:
|
|
553
685
|
logger.error(e)
|
|
554
686
|
raise HTTPException(419, str(f'error: {e}'))
|
|
555
687
|
|
|
688
|
+
return OperationResponse(success=True, history=flow.get_history_state())
|
|
556
689
|
|
|
557
|
-
|
|
690
|
+
|
|
691
|
+
@router.get('/files/available_flow_files', tags=['editor'], response_model=list[FileInfo])
|
|
558
692
|
def get_list_of_saved_flows(path: str):
|
|
559
693
|
"""Scans a directory for saved flow files (`.flowfile`)."""
|
|
560
694
|
try:
|
|
@@ -571,8 +705,8 @@ def get_list_of_saved_flows(path: str):
|
|
|
571
705
|
return []
|
|
572
706
|
|
|
573
707
|
|
|
574
|
-
@router.get('/node_list', response_model=
|
|
575
|
-
def get_node_list() ->
|
|
708
|
+
@router.get('/node_list', response_model=list[schemas.NodeTemplate])
|
|
709
|
+
def get_node_list() -> list[schemas.NodeTemplate]:
|
|
576
710
|
"""Retrieves the list of all available node types and their templates."""
|
|
577
711
|
return nodes_list
|
|
578
712
|
|
|
@@ -612,6 +746,91 @@ def get_description_node(flow_id: int, node_id: int):
|
|
|
612
746
|
return node.setting_input.description
|
|
613
747
|
|
|
614
748
|
|
|
749
|
+
@router.post('/node/reference/', tags=['editor'])
|
|
750
|
+
def update_reference_node(flow_id: int, node_id: int, reference: str = Body(...)):
|
|
751
|
+
"""Updates the reference identifier for a specific node.
|
|
752
|
+
|
|
753
|
+
The reference must be:
|
|
754
|
+
- Lowercase only
|
|
755
|
+
- No spaces allowed
|
|
756
|
+
- Unique across all nodes in the flow
|
|
757
|
+
"""
|
|
758
|
+
try:
|
|
759
|
+
flow = flow_file_handler.get_flow(flow_id)
|
|
760
|
+
node = flow.get_node(node_id)
|
|
761
|
+
except:
|
|
762
|
+
raise HTTPException(404, 'Could not find the node')
|
|
763
|
+
if node is None:
|
|
764
|
+
raise HTTPException(404, 'Could not find the node')
|
|
765
|
+
|
|
766
|
+
# Handle empty reference (allow clearing)
|
|
767
|
+
if reference == "" or reference is None:
|
|
768
|
+
node.setting_input.node_reference = None
|
|
769
|
+
return True
|
|
770
|
+
|
|
771
|
+
# Validate: lowercase only, no spaces
|
|
772
|
+
if " " in reference:
|
|
773
|
+
raise HTTPException(422, 'Reference cannot contain spaces')
|
|
774
|
+
if reference != reference.lower():
|
|
775
|
+
raise HTTPException(422, 'Reference must be lowercase')
|
|
776
|
+
|
|
777
|
+
# Validate: unique across all nodes in the flow
|
|
778
|
+
for other_node in flow.nodes:
|
|
779
|
+
if other_node.node_id != node_id:
|
|
780
|
+
other_ref = getattr(other_node.setting_input, 'node_reference', None)
|
|
781
|
+
if other_ref and other_ref == reference:
|
|
782
|
+
raise HTTPException(422, f'Reference "{reference}" is already used by another node')
|
|
783
|
+
|
|
784
|
+
node.setting_input.node_reference = reference
|
|
785
|
+
return True
|
|
786
|
+
|
|
787
|
+
|
|
788
|
+
@router.get('/node/reference', tags=['editor'])
|
|
789
|
+
def get_reference_node(flow_id: int, node_id: int):
|
|
790
|
+
"""Retrieves the reference identifier for a specific node."""
|
|
791
|
+
try:
|
|
792
|
+
node = flow_file_handler.get_flow(flow_id).get_node(node_id)
|
|
793
|
+
except:
|
|
794
|
+
raise HTTPException(404, 'Could not find the node')
|
|
795
|
+
if node is None:
|
|
796
|
+
raise HTTPException(404, 'Could not find the node')
|
|
797
|
+
return node.setting_input.node_reference or ""
|
|
798
|
+
|
|
799
|
+
|
|
800
|
+
@router.get('/node/validate_reference', tags=['editor'])
|
|
801
|
+
def validate_node_reference(flow_id: int, node_id: int, reference: str):
|
|
802
|
+
"""Validates if a reference is valid and unique for a node.
|
|
803
|
+
|
|
804
|
+
Returns:
|
|
805
|
+
Dict with 'valid' (bool) and 'error' (str or None) fields.
|
|
806
|
+
"""
|
|
807
|
+
try:
|
|
808
|
+
flow = flow_file_handler.get_flow(flow_id)
|
|
809
|
+
except:
|
|
810
|
+
raise HTTPException(404, 'Could not find the flow')
|
|
811
|
+
|
|
812
|
+
# Handle empty reference (always valid - means use default)
|
|
813
|
+
if reference == "" or reference is None:
|
|
814
|
+
return {"valid": True, "error": None}
|
|
815
|
+
|
|
816
|
+
# Validate: lowercase only
|
|
817
|
+
if reference != reference.lower():
|
|
818
|
+
return {"valid": False, "error": "Reference must be lowercase"}
|
|
819
|
+
|
|
820
|
+
# Validate: no spaces
|
|
821
|
+
if " " in reference:
|
|
822
|
+
return {"valid": False, "error": "Reference cannot contain spaces"}
|
|
823
|
+
|
|
824
|
+
# Validate: unique across all nodes in the flow
|
|
825
|
+
for other_node in flow.nodes:
|
|
826
|
+
if other_node.node_id != node_id:
|
|
827
|
+
other_ref = getattr(other_node.setting_input, 'node_reference', None)
|
|
828
|
+
if other_ref and other_ref == reference:
|
|
829
|
+
return {"valid": False, "error": f'Reference "{reference}" is already used by another node'}
|
|
830
|
+
|
|
831
|
+
return {"valid": True, "error": None}
|
|
832
|
+
|
|
833
|
+
|
|
615
834
|
@router.get('/node/data', response_model=output_model.TableExample, tags=['editor'])
|
|
616
835
|
def get_table_example(flow_id: int, node_id: int):
|
|
617
836
|
"""Retrieves a data preview (schema and sample rows) for a node's output."""
|
|
@@ -620,8 +839,8 @@ def get_table_example(flow_id: int, node_id: int):
|
|
|
620
839
|
return node.get_table_example(True)
|
|
621
840
|
|
|
622
841
|
|
|
623
|
-
@router.get('/node/downstream_node_ids', response_model=
|
|
624
|
-
async def get_downstream_node_ids(flow_id: int, node_id: int) ->
|
|
842
|
+
@router.get('/node/downstream_node_ids', response_model=list[int], tags=['editor'])
|
|
843
|
+
async def get_downstream_node_ids(flow_id: int, node_id: int) -> list[int]:
|
|
625
844
|
"""Gets a list of all node IDs that are downstream dependencies of a given node."""
|
|
626
845
|
flow = flow_file_handler.get_flow(flow_id)
|
|
627
846
|
node = flow.get_node(node_id)
|
|
@@ -648,7 +867,7 @@ def save_flow(flow_id: int, flow_path: str = None):
|
|
|
648
867
|
|
|
649
868
|
|
|
650
869
|
@router.get('/flow_data', tags=['manager'])
|
|
651
|
-
def get_flow_frontend_data(flow_id:
|
|
870
|
+
def get_flow_frontend_data(flow_id: int | None = 1):
|
|
652
871
|
"""Retrieves the data needed to render the flow graph in the frontend."""
|
|
653
872
|
flow = flow_file_handler.get_flow(flow_id)
|
|
654
873
|
if flow is None:
|
|
@@ -657,7 +876,7 @@ def get_flow_frontend_data(flow_id: Optional[int] = 1):
|
|
|
657
876
|
|
|
658
877
|
|
|
659
878
|
@router.get('/flow_settings', tags=['manager'], response_model=schemas.FlowSettings)
|
|
660
|
-
def get_flow_settings(flow_id:
|
|
879
|
+
def get_flow_settings(flow_id: int | None = 1) -> schemas.FlowSettings:
|
|
661
880
|
"""Retrieves the main settings for a flow."""
|
|
662
881
|
flow = flow_file_handler.get_flow(flow_id)
|
|
663
882
|
if flow is None:
|