diff --git a/backend/.sqlx/query-04f8d738b1073b8c58db0965e8fdcdb872dce2c1872359c1bebb553a29ba1637.json b/backend/.sqlx/query-04f8d738b1073b8c58db0965e8fdcdb872dce2c1872359c1bebb553a29ba1637.json deleted file mode 100644 index b593669d35..0000000000 --- a/backend/.sqlx/query-04f8d738b1073b8c58db0965e8fdcdb872dce2c1872359c1bebb553a29ba1637.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n UPDATE \n workspace_settings\n SET\n error_handler = NULL,\n error_handler_extra_args = NULL,\n error_handler_muted_on_cancel = NULL\n WHERE \n workspace_id = $1\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [] - }, - "hash": "04f8d738b1073b8c58db0965e8fdcdb872dce2c1872359c1bebb553a29ba1637" -} diff --git a/backend/.sqlx/query-08f288d2781d823e109a9e5b8848234ca7d1efeee9661f3901f298da375e73f7.json b/backend/.sqlx/query-08f288d2781d823e109a9e5b8848234ca7d1efeee9661f3901f298da375e73f7.json index 0ef59c340c..fac1d666f7 100644 --- a/backend/.sqlx/query-08f288d2781d823e109a9e5b8848234ca7d1efeee9661f3901f298da375e73f7.json +++ b/backend/.sqlx/query-08f288d2781d823e109a9e5b8848234ca7d1efeee9661f3901f298da375e73f7.json @@ -147,6 +147,16 @@ "ordinal": 28, "name": "ducklake", "type_info": "Jsonb" + }, + { + "ordinal": 29, + "name": "auto_add_instance_groups", + "type_info": "TextArray" + }, + { + "ordinal": 30, + "name": "auto_add_instance_groups_roles", + "type_info": "Jsonb" } ], "parameters": { @@ -183,6 +193,8 @@ true, true, false, + true, + true, true ] }, diff --git a/backend/.sqlx/query-0a56301b5aaf57339cb2904c8f617366b74e891034d32f2867ccb019da869fc8.json b/backend/.sqlx/query-0a56301b5aaf57339cb2904c8f617366b74e891034d32f2867ccb019da869fc8.json deleted file mode 100644 index 159789f439..0000000000 --- a/backend/.sqlx/query-0a56301b5aaf57339cb2904c8f617366b74e891034d32f2867ccb019da869fc8.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO flow\n (workspace_id, path, summary, description, archived, extra_perms, dependency_job, draft_only, tag, ws_error_handler_muted, dedicated_worker, timeout, visible_to_runner_only, on_behalf_of_email, concurrency_key, versions, value, schema, edited_by, edited_at) \n SELECT workspace_id, REGEXP_REPLACE(path,'u/' || $2 || '/(.*)','u/' || $1 || '/\\1'), summary, description, archived, extra_perms, dependency_job, draft_only, tag, ws_error_handler_muted, dedicated_worker, timeout, visible_to_runner_only, on_behalf_of_email, concurrency_key, versions, value, schema, edited_by, edited_at\n FROM flow \n WHERE path LIKE ('u/' || $2 || '/%') AND workspace_id = $3", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Text", - "Text" - ] - }, - "nullable": [] - }, - "hash": "0a56301b5aaf57339cb2904c8f617366b74e891034d32f2867ccb019da869fc8" -} diff --git a/backend/.sqlx/query-e822d186203fe809b764007ad7c02870a3b7d93ae43b40ac2cd3181dffab0837.json b/backend/.sqlx/query-0d7ce0397ef15c9d6cdaeaa2730a9e27fb7387ca24980df1481e6a94622ef006.json similarity index 57% rename from backend/.sqlx/query-e822d186203fe809b764007ad7c02870a3b7d93ae43b40ac2cd3181dffab0837.json rename to backend/.sqlx/query-0d7ce0397ef15c9d6cdaeaa2730a9e27fb7387ca24980df1481e6a94622ef006.json index 8974494ddd..1c8c6bb12c 100644 --- a/backend/.sqlx/query-e822d186203fe809b764007ad7c02870a3b7d93ae43b40ac2cd3181dffab0837.json +++ b/backend/.sqlx/query-0d7ce0397ef15c9d6cdaeaa2730a9e27fb7387ca24980df1481e6a94622ef006.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO usr (workspace_id, username, email, is_admin, operator) VALUES ($1, $2, $3, false, $4) ON CONFLICT DO NOTHING", + "query": "INSERT INTO usr (workspace_id, username, email, is_admin, operator, added_via) VALUES ($1, $2, $3, false, $4, $5) ON CONFLICT DO NOTHING", "describe": { "columns": [], "parameters": { @@ -8,10 +8,11 @@ "Varchar", "Varchar", "Varchar", - "Bool" + "Bool", + "Jsonb" ] }, "nullable": [] }, - "hash": "e822d186203fe809b764007ad7c02870a3b7d93ae43b40ac2cd3181dffab0837" + "hash": "0d7ce0397ef15c9d6cdaeaa2730a9e27fb7387ca24980df1481e6a94622ef006" } diff --git a/backend/.sqlx/query-0fa105c49c8345916716514444bd3616ae4d114216c659233fbbc3c047e6b30a.json b/backend/.sqlx/query-0fa105c49c8345916716514444bd3616ae4d114216c659233fbbc3c047e6b30a.json new file mode 100644 index 0000000000..c604c19f04 --- /dev/null +++ b/backend/.sqlx/query-0fa105c49c8345916716514444bd3616ae4d114216c659233fbbc3c047e6b30a.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE\n workspace_settings\n SET\n error_handler = NULL,\n error_handler_extra_args = NULL,\n error_handler_muted_on_cancel = NULL\n WHERE\n workspace_id = $1\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [] + }, + "hash": "0fa105c49c8345916716514444bd3616ae4d114216c659233fbbc3c047e6b30a" +} diff --git a/backend/.sqlx/query-10f6d3ffd7406146572b1becdce5c8da5242b58f6ce46ab10296cff9d6a3a6c4.json b/backend/.sqlx/query-10f6d3ffd7406146572b1becdce5c8da5242b58f6ce46ab10296cff9d6a3a6c4.json new file mode 100644 index 0000000000..98bafc734c --- /dev/null +++ b/backend/.sqlx/query-10f6d3ffd7406146572b1becdce5c8da5242b58f6ce46ab10296cff9d6a3a6c4.json @@ -0,0 +1,32 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT name, summary, array_remove(array_agg(email_to_igroup.email), null) as emails FROM email_to_igroup RIGHT JOIN instance_group ON instance_group.name = email_to_igroup.igroup GROUP BY name, summary", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "name", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "summary", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "emails", + "type_info": "VarcharArray" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false, + true, + null + ] + }, + "hash": "10f6d3ffd7406146572b1becdce5c8da5242b58f6ce46ab10296cff9d6a3a6c4" +} diff --git a/backend/.sqlx/query-18e550f4ec23d465632449b88c4b25931f145f771b93828e8e6dfcc1f906443d.json b/backend/.sqlx/query-18e550f4ec23d465632449b88c4b25931f145f771b93828e8e6dfcc1f906443d.json new file mode 100644 index 0000000000..bfc9587a1e --- /dev/null +++ b/backend/.sqlx/query-18e550f4ec23d465632449b88c4b25931f145f771b93828e8e6dfcc1f906443d.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COALESCE(auto_add_instance_groups, '{}') FROM workspace_settings WHERE workspace_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "coalesce", + "type_info": "TextArray" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "18e550f4ec23d465632449b88c4b25931f145f771b93828e8e6dfcc1f906443d" +} diff --git a/backend/.sqlx/query-1e6c125c884002a1565b11a2c308ce89f9726d21bbb3cc712ad0b6450cbb44e6.json b/backend/.sqlx/query-1e6c125c884002a1565b11a2c308ce89f9726d21bbb3cc712ad0b6450cbb44e6.json new file mode 100644 index 0000000000..2a4c87da6a --- /dev/null +++ b/backend/.sqlx/query-1e6c125c884002a1565b11a2c308ce89f9726d21bbb3cc712ad0b6450cbb44e6.json @@ -0,0 +1,38 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n ig.name as group_name,\n ws.workspace_id,\n w.name as workspace_name,\n ws.auto_add_instance_groups_roles->ig.name as role\n FROM instance_group ig\n INNER JOIN workspace_settings ws ON ws.auto_add_instance_groups IS NOT NULL\n AND ig.name = ANY(ws.auto_add_instance_groups)\n INNER JOIN workspace w ON w.id = ws.workspace_id AND w.deleted = false\n ORDER BY ig.name, ws.workspace_id\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "group_name", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "workspace_id", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "workspace_name", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "role", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false, + false, + false, + null + ] + }, + "hash": "1e6c125c884002a1565b11a2c308ce89f9726d21bbb3cc712ad0b6450cbb44e6" +} diff --git a/backend/.sqlx/query-21099fabde943edc90d3a0125e8490691b2701d11f918da3bb5ae610d5c023a0.json b/backend/.sqlx/query-21099fabde943edc90d3a0125e8490691b2701d11f918da3bb5ae610d5c023a0.json new file mode 100644 index 0000000000..ccfddce0ff --- /dev/null +++ b/backend/.sqlx/query-21099fabde943edc90d3a0125e8490691b2701d11f918da3bb5ae610d5c023a0.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT email FROM email_to_igroup WHERE igroup = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "email", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "21099fabde943edc90d3a0125e8490691b2701d11f918da3bb5ae610d5c023a0" +} diff --git a/backend/.sqlx/query-2132a715995f2775917c59c01ba66f2e472a2347f25cc7109f36a805806ee6e2.json b/backend/.sqlx/query-2132a715995f2775917c59c01ba66f2e472a2347f25cc7109f36a805806ee6e2.json new file mode 100644 index 0000000000..673b8f4574 --- /dev/null +++ b/backend/.sqlx/query-2132a715995f2775917c59c01ba66f2e472a2347f25cc7109f36a805806ee6e2.json @@ -0,0 +1,29 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT username, added_via\n FROM usr\n WHERE workspace_id = $1 AND email = $2\n AND added_via->>'source' = 'instance_group'\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "username", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "added_via", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false, + true + ] + }, + "hash": "2132a715995f2775917c59c01ba66f2e472a2347f25cc7109f36a805806ee6e2" +} diff --git a/backend/.sqlx/query-2549197c6750bb1a20041b2d7c2654a788a32a9282ed314c2b3bcafe6550efca.json b/backend/.sqlx/query-2549197c6750bb1a20041b2d7c2654a788a32a9282ed314c2b3bcafe6550efca.json deleted file mode 100644 index b80ece371b..0000000000 --- a/backend/.sqlx/query-2549197c6750bb1a20041b2d7c2654a788a32a9282ed314c2b3bcafe6550efca.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n UPDATE \n workspace_settings\n SET\n error_handler = $1,\n error_handler_extra_args = $2,\n error_handler_muted_on_cancel = $3\n WHERE \n workspace_id = $4\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Json", - "Bool", - "Text" - ] - }, - "nullable": [] - }, - "hash": "2549197c6750bb1a20041b2d7c2654a788a32a9282ed314c2b3bcafe6550efca" -} diff --git a/backend/.sqlx/query-31db242523c534b2abbb5f4985f56dd4493d19611ed988d694d9f4289dc29c48.json b/backend/.sqlx/query-31db242523c534b2abbb5f4985f56dd4493d19611ed988d694d9f4289dc29c48.json new file mode 100644 index 0000000000..6bb55d3c77 --- /dev/null +++ b/backend/.sqlx/query-31db242523c534b2abbb5f4985f56dd4493d19611ed988d694d9f4289dc29c48.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE workspace_settings SET auto_add_instance_groups = NULL, auto_add_instance_groups_roles = NULL WHERE workspace_id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [] + }, + "hash": "31db242523c534b2abbb5f4985f56dd4493d19611ed988d694d9f4289dc29c48" +} diff --git a/backend/.sqlx/query-3ebc91867be0a4830ef4dbbe833aed348f78e3cb68fb1f3c855a491bdcda5017.json b/backend/.sqlx/query-3ebc91867be0a4830ef4dbbe833aed348f78e3cb68fb1f3c855a491bdcda5017.json new file mode 100644 index 0000000000..73d8f1858c --- /dev/null +++ b/backend/.sqlx/query-3ebc91867be0a4830ef4dbbe833aed348f78e3cb68fb1f3c855a491bdcda5017.json @@ -0,0 +1,28 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n workspace_id,\n auto_add_instance_groups_roles\n FROM workspace_settings\n WHERE\n auto_add_instance_groups IS NOT NULL\n AND $1 = ANY(auto_add_instance_groups)\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "workspace_id", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "auto_add_instance_groups_roles", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + true + ] + }, + "hash": "3ebc91867be0a4830ef4dbbe833aed348f78e3cb68fb1f3c855a491bdcda5017" +} diff --git a/backend/.sqlx/query-3fdfcab1a54c166b1d8d43215d61268a251160db4630f0342522091668f36af0.json b/backend/.sqlx/query-3fdfcab1a54c166b1d8d43215d61268a251160db4630f0342522091668f36af0.json deleted file mode 100644 index be7e8608de..0000000000 --- a/backend/.sqlx/query-3fdfcab1a54c166b1d8d43215d61268a251160db4630f0342522091668f36af0.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT usr.username \n FROM usr_to_group LEFT JOIN usr ON usr_to_group.usr = usr.username AND usr_to_group.workspace_id = $2\n WHERE group_ = $1 AND usr.workspace_id = $2 AND usr_to_group.workspace_id = $2", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "username", - "type_info": "Varchar" - } - ], - "parameters": { - "Left": [ - "Text", - "Text" - ] - }, - "nullable": [ - false - ] - }, - "hash": "3fdfcab1a54c166b1d8d43215d61268a251160db4630f0342522091668f36af0" -} diff --git a/backend/.sqlx/query-404233e74aaafd987879c6c87d1fd80928e6fade09e60a9a2d35121c81885cca.json b/backend/.sqlx/query-404233e74aaafd987879c6c87d1fd80928e6fade09e60a9a2d35121c81885cca.json new file mode 100644 index 0000000000..2ed01e7a44 --- /dev/null +++ b/backend/.sqlx/query-404233e74aaafd987879c6c87d1fd80928e6fade09e60a9a2d35121c81885cca.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE workspace_settings SET auto_add_instance_groups = $2, auto_add_instance_groups_roles = $3 WHERE workspace_id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "TextArray", + "Jsonb" + ] + }, + "nullable": [] + }, + "hash": "404233e74aaafd987879c6c87d1fd80928e6fade09e60a9a2d35121c81885cca" +} diff --git a/backend/.sqlx/query-472f224dc9e17d2c50cb9db13c34cc1fb9adb6d0ea36cf223541adb7cac17bdd.json b/backend/.sqlx/query-472f224dc9e17d2c50cb9db13c34cc1fb9adb6d0ea36cf223541adb7cac17bdd.json new file mode 100644 index 0000000000..b60ecae184 --- /dev/null +++ b/backend/.sqlx/query-472f224dc9e17d2c50cb9db13c34cc1fb9adb6d0ea36cf223541adb7cac17bdd.json @@ -0,0 +1,35 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT workspace_id, username, email\n FROM usr\n WHERE email = $1\n AND added_via->>'source' = 'instance_group'\n AND added_via->>'group' = $2\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "workspace_id", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "username", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "email", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "472f224dc9e17d2c50cb9db13c34cc1fb9adb6d0ea36cf223541adb7cac17bdd" +} diff --git a/backend/.sqlx/query-4a446b97cf813226d25ca40cefca2999cdcf938f817327e9168b3edb0fce7fb7.json b/backend/.sqlx/query-4a446b97cf813226d25ca40cefca2999cdcf938f817327e9168b3edb0fce7fb7.json new file mode 100644 index 0000000000..f984c967dd --- /dev/null +++ b/backend/.sqlx/query-4a446b97cf813226d25ca40cefca2999cdcf938f817327e9168b3edb0fce7fb7.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE workspace_settings SET\n auto_add_instance_groups = array_remove(auto_add_instance_groups, $1),\n auto_add_instance_groups_roles = auto_add_instance_groups_roles - $1\n WHERE workspace_id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "4a446b97cf813226d25ca40cefca2999cdcf938f817327e9168b3edb0fce7fb7" +} diff --git a/backend/.sqlx/query-a7b028e832396ee4d1ad6dfd44ba6134344f3eb37dbf0254154eba31b9cc2ed3.json b/backend/.sqlx/query-51cfe6efb154934f8cfbe77a7313891ef5c46a7febe4125d164a5ae5b55af2d6.json similarity index 50% rename from backend/.sqlx/query-a7b028e832396ee4d1ad6dfd44ba6134344f3eb37dbf0254154eba31b9cc2ed3.json rename to backend/.sqlx/query-51cfe6efb154934f8cfbe77a7313891ef5c46a7febe4125d164a5ae5b55af2d6.json index 2de5f54b1a..107e77d728 100644 --- a/backend/.sqlx/query-a7b028e832396ee4d1ad6dfd44ba6134344f3eb37dbf0254154eba31b9cc2ed3.json +++ b/backend/.sqlx/query-51cfe6efb154934f8cfbe77a7313891ef5c46a7febe4125d164a5ae5b55af2d6.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT usage.usage FROM usage \n WHERE is_workspace = false \n AND month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date)\n AND id = $1", + "query": "\n SELECT usage.usage FROM usage\n WHERE is_workspace = false\n AND month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date)\n AND id = $1", "describe": { "columns": [ { @@ -18,5 +18,5 @@ false ] }, - "hash": "a7b028e832396ee4d1ad6dfd44ba6134344f3eb37dbf0254154eba31b9cc2ed3" + "hash": "51cfe6efb154934f8cfbe77a7313891ef5c46a7febe4125d164a5ae5b55af2d6" } diff --git a/backend/.sqlx/query-55cb03040bc2a8c53dd7fbb42bbdcc40f463cbc52d94ed9315cf9a547d4c89f2.json b/backend/.sqlx/query-55cb03040bc2a8c53dd7fbb42bbdcc40f463cbc52d94ed9315cf9a547d4c89f2.json index f589df856d..0051151fc0 100644 --- a/backend/.sqlx/query-55cb03040bc2a8c53dd7fbb42bbdcc40f463cbc52d94ed9315cf9a547d4c89f2.json +++ b/backend/.sqlx/query-55cb03040bc2a8c53dd7fbb42bbdcc40f463cbc52d94ed9315cf9a547d4c89f2.json @@ -147,6 +147,16 @@ "ordinal": 28, "name": "ducklake", "type_info": "Jsonb" + }, + { + "ordinal": 29, + "name": "auto_add_instance_groups", + "type_info": "TextArray" + }, + { + "ordinal": 30, + "name": "auto_add_instance_groups_roles", + "type_info": "Jsonb" } ], "parameters": { @@ -183,6 +193,8 @@ true, true, false, + true, + true, true ] }, diff --git a/backend/.sqlx/query-5d6adbe21b9f8dd984d1bfc750fb81763d8650c1316bb0b20816f1a5d61a678c.json b/backend/.sqlx/query-5d6adbe21b9f8dd984d1bfc750fb81763d8650c1316bb0b20816f1a5d61a678c.json index 78dea070ea..09775dcc3a 100644 --- a/backend/.sqlx/query-5d6adbe21b9f8dd984d1bfc750fb81763d8650c1316bb0b20816f1a5d61a678c.json +++ b/backend/.sqlx/query-5d6adbe21b9f8dd984d1bfc750fb81763d8650c1316bb0b20816f1a5d61a678c.json @@ -42,6 +42,11 @@ "ordinal": 7, "name": "role", "type_info": "Varchar" + }, + { + "ordinal": 8, + "name": "added_via", + "type_info": "Jsonb" } ], "parameters": { @@ -57,6 +62,7 @@ false, false, false, + true, true ] }, diff --git a/backend/.sqlx/query-60b3a59805d463a61eed68072d1ea032b00fc9bd7a6db22f530f67eb9730fa3b.json b/backend/.sqlx/query-60b3a59805d463a61eed68072d1ea032b00fc9bd7a6db22f530f67eb9730fa3b.json index 70ae06505c..3a635ab004 100644 --- a/backend/.sqlx/query-60b3a59805d463a61eed68072d1ea032b00fc9bd7a6db22f530f67eb9730fa3b.json +++ b/backend/.sqlx/query-60b3a59805d463a61eed68072d1ea032b00fc9bd7a6db22f530f67eb9730fa3b.json @@ -42,6 +42,11 @@ "ordinal": 7, "name": "role", "type_info": "Varchar" + }, + { + "ordinal": 8, + "name": "added_via", + "type_info": "Jsonb" } ], "parameters": { @@ -58,6 +63,7 @@ false, false, false, + true, true ] }, diff --git a/backend/.sqlx/query-6aabe704395c9be30c86d15a5d22f3509b4fcea56227b019588837132b64d58b.json b/backend/.sqlx/query-6aabe704395c9be30c86d15a5d22f3509b4fcea56227b019588837132b64d58b.json index 27e21028d4..6e1b36a97c 100644 --- a/backend/.sqlx/query-6aabe704395c9be30c86d15a5d22f3509b4fcea56227b019588837132b64d58b.json +++ b/backend/.sqlx/query-6aabe704395c9be30c86d15a5d22f3509b4fcea56227b019588837132b64d58b.json @@ -45,11 +45,16 @@ }, { "ordinal": 8, + "name": "added_via", + "type_info": "Jsonb" + }, + { + "ordinal": 9, "name": "super_admin", "type_info": "Bool" }, { - "ordinal": 9, + "ordinal": 10, "name": "name", "type_info": "Varchar" } @@ -69,6 +74,7 @@ false, false, true, + true, false, true ] diff --git a/backend/.sqlx/query-388d6fd335a3f8a405b2d465892cf21a68d4b50ace25ef88c4cdf5b347c3d5eb.json b/backend/.sqlx/query-6b2820973026b1628cb93fda943bc389a261a3bb975c86ac02dd1c552da92295.json similarity index 67% rename from backend/.sqlx/query-388d6fd335a3f8a405b2d465892cf21a68d4b50ace25ef88c4cdf5b347c3d5eb.json rename to backend/.sqlx/query-6b2820973026b1628cb93fda943bc389a261a3bb975c86ac02dd1c552da92295.json index 5965bc591a..350e0f9ff3 100644 --- a/backend/.sqlx/query-388d6fd335a3f8a405b2d465892cf21a68d4b50ace25ef88c4cdf5b347c3d5eb.json +++ b/backend/.sqlx/query-6b2820973026b1628cb93fda943bc389a261a3bb975c86ac02dd1c552da92295.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT EXISTS(SELECT 1 FROM group_ WHERE (group_.extra_perms ->> CONCAT('u/', $1::text))::boolean AND name = $2 AND workspace_id = $4) OR exists(\n SELECT 1 FROM group_ g, jsonb_each_text(g.extra_perms) f \n WHERE $2 = g.name AND $4 = g.workspace_id AND SPLIT_PART(key, '/', 1) = 'g' AND key = ANY($3::text[])\n AND value::boolean)", + "query": "SELECT EXISTS(SELECT 1 FROM group_ WHERE (group_.extra_perms ->> CONCAT('u/', $1::text))::boolean AND name = $2 AND workspace_id = $4) OR exists(\n SELECT 1 FROM group_ g, jsonb_each_text(g.extra_perms) f\n WHERE $2 = g.name AND $4 = g.workspace_id AND SPLIT_PART(key, '/', 1) = 'g' AND key = ANY($3::text[])\n AND value::boolean)", "describe": { "columns": [ { @@ -21,5 +21,5 @@ null ] }, - "hash": "388d6fd335a3f8a405b2d465892cf21a68d4b50ace25ef88c4cdf5b347c3d5eb" + "hash": "6b2820973026b1628cb93fda943bc389a261a3bb975c86ac02dd1c552da92295" } diff --git a/backend/.sqlx/query-6f36c05e6097066441acab227c0d5206d5a9a9828b04e4c107f1ecab4a9363d4.json b/backend/.sqlx/query-6f36c05e6097066441acab227c0d5206d5a9a9828b04e4c107f1ecab4a9363d4.json new file mode 100644 index 0000000000..852378e548 --- /dev/null +++ b/backend/.sqlx/query-6f36c05e6097066441acab227c0d5206d5a9a9828b04e4c107f1ecab4a9363d4.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT usr.username\n FROM usr_to_group LEFT JOIN usr ON usr_to_group.usr = usr.username AND usr_to_group.workspace_id = $2\n WHERE group_ = $1 AND usr.workspace_id = $2 AND usr_to_group.workspace_id = $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "username", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "6f36c05e6097066441acab227c0d5206d5a9a9828b04e4c107f1ecab4a9363d4" +} diff --git a/backend/.sqlx/query-711c72a72298bb656882b35071e910dab721cda070f9c39bba3729bdc1467496.json b/backend/.sqlx/query-711c72a72298bb656882b35071e910dab721cda070f9c39bba3729bdc1467496.json new file mode 100644 index 0000000000..908368edd5 --- /dev/null +++ b/backend/.sqlx/query-711c72a72298bb656882b35071e910dab721cda070f9c39bba3729bdc1467496.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT workspace_id FROM workspace_settings WHERE auto_add_instance_groups IS NOT NULL AND $1 = ANY(auto_add_instance_groups)", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "workspace_id", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "711c72a72298bb656882b35071e910dab721cda070f9c39bba3729bdc1467496" +} diff --git a/backend/.sqlx/query-bff39cc57aba0729ddef1d53f3806c6736556f0a14b489d6708f9879393f9ea3.json b/backend/.sqlx/query-89940a53f29b173b6a8717f057a81a4f2edad152581950fdd80d758a0d242c17.json similarity index 79% rename from backend/.sqlx/query-bff39cc57aba0729ddef1d53f3806c6736556f0a14b489d6708f9879393f9ea3.json rename to backend/.sqlx/query-89940a53f29b173b6a8717f057a81a4f2edad152581950fdd80d758a0d242c17.json index 11e498f38d..25dd18003c 100644 --- a/backend/.sqlx/query-bff39cc57aba0729ddef1d53f3806c6736556f0a14b489d6708f9879393f9ea3.json +++ b/backend/.sqlx/query-89940a53f29b173b6a8717f057a81a4f2edad152581950fdd80d758a0d242c17.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT usr.email, usage.executions\n FROM usr\n , LATERAL (\n SELECT COALESCE(SUM(duration_ms + 1000)/1000 , 0)::BIGINT executions\n FROM v2_as_completed_job\n WHERE workspace_id = $1\n AND job_kind NOT IN ('flow', 'flowpreview', 'flownode')\n AND email = usr.email\n AND now() - '1 week'::interval < created_at \n ) usage\n WHERE workspace_id = $1\n ", + "query": "\n SELECT usr.email, usage.executions\n FROM usr\n , LATERAL (\n SELECT COALESCE(SUM(duration_ms + 1000)/1000 , 0)::BIGINT executions\n FROM v2_as_completed_job\n WHERE workspace_id = $1\n AND job_kind NOT IN ('flow', 'flowpreview', 'flownode')\n AND email = usr.email\n AND now() - '1 week'::interval < created_at\n ) usage\n WHERE workspace_id = $1\n ", "describe": { "columns": [ { @@ -24,5 +24,5 @@ null ] }, - "hash": "bff39cc57aba0729ddef1d53f3806c6736556f0a14b489d6708f9879393f9ea3" + "hash": "89940a53f29b173b6a8717f057a81a4f2edad152581950fdd80d758a0d242c17" } diff --git a/backend/.sqlx/query-94feb54cc965f19bfaf09966fd431b925c565f08c100bea812d3ac0e28664ccb.json b/backend/.sqlx/query-94feb54cc965f19bfaf09966fd431b925c565f08c100bea812d3ac0e28664ccb.json new file mode 100644 index 0000000000..715fbce15e --- /dev/null +++ b/backend/.sqlx/query-94feb54cc965f19bfaf09966fd431b925c565f08c100bea812d3ac0e28664ccb.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE usr SET is_admin = true WHERE workspace_id = $1 AND email = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "94feb54cc965f19bfaf09966fd431b925c565f08c100bea812d3ac0e28664ccb" +} diff --git a/backend/.sqlx/query-9530b234b2ac8f7360552be5e6bb25270e6b4185ce5c7f3c6edd9fbaabf77544.json b/backend/.sqlx/query-9530b234b2ac8f7360552be5e6bb25270e6b4185ce5c7f3c6edd9fbaabf77544.json new file mode 100644 index 0000000000..25a898e2f1 --- /dev/null +++ b/backend/.sqlx/query-9530b234b2ac8f7360552be5e6bb25270e6b4185ce5c7f3c6edd9fbaabf77544.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT DISTINCT email FROM usr WHERE added_via->>'source' = 'instance_group' AND added_via->>'group' = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "email", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "9530b234b2ac8f7360552be5e6bb25270e6b4185ce5c7f3c6edd9fbaabf77544" +} diff --git a/backend/.sqlx/query-97b6e5779661343587c297b275df9e75fd22924ad2dbe124bb361f3e7fd8bfe0.json b/backend/.sqlx/query-97b6e5779661343587c297b275df9e75fd22924ad2dbe124bb361f3e7fd8bfe0.json new file mode 100644 index 0000000000..69ed74c7ae --- /dev/null +++ b/backend/.sqlx/query-97b6e5779661343587c297b275df9e75fd22924ad2dbe124bb361f3e7fd8bfe0.json @@ -0,0 +1,17 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE\n workspace_settings\n SET\n error_handler = $1,\n error_handler_extra_args = $2,\n error_handler_muted_on_cancel = $3\n WHERE\n workspace_id = $4\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Json", + "Bool", + "Text" + ] + }, + "nullable": [] + }, + "hash": "97b6e5779661343587c297b275df9e75fd22924ad2dbe124bb361f3e7fd8bfe0" +} diff --git a/backend/.sqlx/query-8960b73f0d3cbfa0729d24ecbe3f635592feee6a5724fdff662c6ac1f3c6ddc8.json b/backend/.sqlx/query-ac1fd12e9ec92022be38aee0e91e9002e0e810d0e76dce5ced1000a7cb514adb.json similarity index 71% rename from backend/.sqlx/query-8960b73f0d3cbfa0729d24ecbe3f635592feee6a5724fdff662c6ac1f3c6ddc8.json rename to backend/.sqlx/query-ac1fd12e9ec92022be38aee0e91e9002e0e810d0e76dce5ced1000a7cb514adb.json index 1d33f1b2a0..420d3b8573 100644 --- a/backend/.sqlx/query-8960b73f0d3cbfa0729d24ecbe3f635592feee6a5724fdff662c6ac1f3c6ddc8.json +++ b/backend/.sqlx/query-ac1fd12e9ec92022be38aee0e91e9002e0e810d0e76dce5ced1000a7cb514adb.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT \n workspace_id,\n slack_team_id,\n teams_team_id,\n teams_team_name,\n slack_name,\n slack_command_script,\n teams_command_script,\n slack_email,\n auto_invite_domain,\n auto_invite_operator,\n auto_add,\n customer_id,\n plan,\n webhook,\n deploy_to,\n ai_config,\n error_handler,\n error_handler_extra_args,\n error_handler_muted_on_cancel,\n large_file_storage,\n ducklake,\n git_sync,\n deploy_ui,\n default_app,\n default_scripts,\n mute_critical_alerts,\n color,\n operator_settings,\n git_app_installations\n FROM \n workspace_settings\n WHERE \n workspace_id = $1\n ", + "query": "\n SELECT\n workspace_id,\n slack_team_id,\n teams_team_id,\n teams_team_name,\n slack_name,\n slack_command_script,\n teams_command_script,\n slack_email,\n auto_invite_domain,\n auto_invite_operator,\n auto_add,\n customer_id,\n plan,\n webhook,\n deploy_to,\n ai_config,\n error_handler,\n error_handler_extra_args,\n error_handler_muted_on_cancel,\n large_file_storage,\n ducklake,\n git_sync,\n deploy_ui,\n default_app,\n default_scripts,\n mute_critical_alerts,\n color,\n operator_settings,\n git_app_installations,\n auto_add_instance_groups,\n auto_add_instance_groups_roles\n FROM\n workspace_settings\n WHERE\n workspace_id = $1\n ", "describe": { "columns": [ { @@ -147,6 +147,16 @@ "ordinal": 28, "name": "git_app_installations", "type_info": "Jsonb" + }, + { + "ordinal": 29, + "name": "auto_add_instance_groups", + "type_info": "TextArray" + }, + { + "ordinal": 30, + "name": "auto_add_instance_groups_roles", + "type_info": "Jsonb" } ], "parameters": { @@ -183,8 +193,10 @@ true, true, true, - false + false, + true, + true ] }, - "hash": "8960b73f0d3cbfa0729d24ecbe3f635592feee6a5724fdff662c6ac1f3c6ddc8" + "hash": "ac1fd12e9ec92022be38aee0e91e9002e0e810d0e76dce5ced1000a7cb514adb" } diff --git a/backend/.sqlx/query-b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76.json b/backend/.sqlx/query-b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76.json index 54e94cfb8f..99269c9851 100644 --- a/backend/.sqlx/query-b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76.json +++ b/backend/.sqlx/query-b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76.json @@ -18,8 +18,8 @@ "Left": [] }, "nullable": [ - false, - true + true, + false ] }, "hash": "b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76" diff --git a/backend/.sqlx/query-c269f14ae9ae4e96eff9483eb84cccbdfa316e5db051f1d52085a2d5447c81ae.json b/backend/.sqlx/query-c269f14ae9ae4e96eff9483eb84cccbdfa316e5db051f1d52085a2d5447c81ae.json new file mode 100644 index 0000000000..e566e02ac4 --- /dev/null +++ b/backend/.sqlx/query-c269f14ae9ae4e96eff9483eb84cccbdfa316e5db051f1d52085a2d5447c81ae.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO flow\n (workspace_id, path, summary, description, archived, extra_perms, dependency_job, draft_only, tag, ws_error_handler_muted, dedicated_worker, timeout, visible_to_runner_only, on_behalf_of_email, concurrency_key, versions, value, schema, edited_by, edited_at)\n SELECT workspace_id, REGEXP_REPLACE(path,'u/' || $2 || '/(.*)','u/' || $1 || '/\\1'), summary, description, archived, extra_perms, dependency_job, draft_only, tag, ws_error_handler_muted, dedicated_worker, timeout, visible_to_runner_only, on_behalf_of_email, concurrency_key, versions, value, schema, edited_by, edited_at\n FROM flow\n WHERE path LIKE ('u/' || $2 || '/%') AND workspace_id = $3", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "c269f14ae9ae4e96eff9483eb84cccbdfa316e5db051f1d52085a2d5447c81ae" +} diff --git a/backend/.sqlx/query-974c7e623f3dfa440e134eaaa8d029334c0645147200219c39b2c00b30941172.json b/backend/.sqlx/query-ccbf71572dfc60b69a1666d4f1a883ba724d1e9c3303df32b001737b80a4b9f9.json similarity index 72% rename from backend/.sqlx/query-974c7e623f3dfa440e134eaaa8d029334c0645147200219c39b2c00b30941172.json rename to backend/.sqlx/query-ccbf71572dfc60b69a1666d4f1a883ba724d1e9c3303df32b001737b80a4b9f9.json index 0a735b5a2c..cb8ef42144 100644 --- a/backend/.sqlx/query-974c7e623f3dfa440e134eaaa8d029334c0645147200219c39b2c00b30941172.json +++ b/backend/.sqlx/query-ccbf71572dfc60b69a1666d4f1a883ba724d1e9c3303df32b001737b80a4b9f9.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT flow.workspace_id as workspace, flow.path, summary, description, flow_version.schema \n FROM flow \n LEFT JOIN flow_version ON flow_version.id = flow.versions[array_upper(flow.versions, 1)]\n WHERE flow.workspace_id = $1", + "query": "SELECT flow.workspace_id as workspace, flow.path, summary, description, flow_version.schema\n FROM flow\n LEFT JOIN flow_version ON flow_version.id = flow.versions[array_upper(flow.versions, 1)]\n WHERE flow.workspace_id = $1", "describe": { "columns": [ { @@ -42,5 +42,5 @@ true ] }, - "hash": "974c7e623f3dfa440e134eaaa8d029334c0645147200219c39b2c00b30941172" + "hash": "ccbf71572dfc60b69a1666d4f1a883ba724d1e9c3303df32b001737b80a4b9f9" } diff --git a/backend/.sqlx/query-e5fb3531f8bc7ef1f7484524f8c3bc9c48f71a44827ba0d01ac5588dc31082a2.json b/backend/.sqlx/query-e5fb3531f8bc7ef1f7484524f8c3bc9c48f71a44827ba0d01ac5588dc31082a2.json index f70cbfde21..cdeb30f672 100644 --- a/backend/.sqlx/query-e5fb3531f8bc7ef1f7484524f8c3bc9c48f71a44827ba0d01ac5588dc31082a2.json +++ b/backend/.sqlx/query-e5fb3531f8bc7ef1f7484524f8c3bc9c48f71a44827ba0d01ac5588dc31082a2.json @@ -42,6 +42,11 @@ "ordinal": 7, "name": "role", "type_info": "Varchar" + }, + { + "ordinal": 8, + "name": "added_via", + "type_info": "Jsonb" } ], "parameters": { @@ -57,6 +62,7 @@ false, false, false, + true, true ] }, diff --git a/backend/.sqlx/query-ee182378d9a760c3593b483703ec1682488ca9e6e402ec41abaa6ae3ca263854.json b/backend/.sqlx/query-ee182378d9a760c3593b483703ec1682488ca9e6e402ec41abaa6ae3ca263854.json new file mode 100644 index 0000000000..ec170d17c8 --- /dev/null +++ b/backend/.sqlx/query-ee182378d9a760c3593b483703ec1682488ca9e6e402ec41abaa6ae3ca263854.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT email_to_igroup.email\n FROM email_to_igroup\n INNER JOIN instance_group ON instance_group.name = email_to_igroup.igroup\n WHERE instance_group.name = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "email", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "ee182378d9a760c3593b483703ec1682488ca9e6e402ec41abaa6ae3ca263854" +} diff --git a/backend/.sqlx/query-f582cac90b4b7d732956b74eebc51323ef8acd3f627e7517451fcc72998d22bc.json b/backend/.sqlx/query-f582cac90b4b7d732956b74eebc51323ef8acd3f627e7517451fcc72998d22bc.json new file mode 100644 index 0000000000..5bfc4710c8 --- /dev/null +++ b/backend/.sqlx/query-f582cac90b4b7d732956b74eebc51323ef8acd3f627e7517451fcc72998d22bc.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE usr SET added_via = $1 WHERE workspace_id = $2 AND email = $3", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Jsonb", + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "f582cac90b4b7d732956b74eebc51323ef8acd3f627e7517451fcc72998d22bc" +} diff --git a/backend/.sqlx/query-fec6d5674dc6b5a6a0ece419c40508835affcb7679a48f2a443777e829bd1e74.json b/backend/.sqlx/query-fec6d5674dc6b5a6a0ece419c40508835affcb7679a48f2a443777e829bd1e74.json index 649ab85650..de2e819af2 100644 --- a/backend/.sqlx/query-fec6d5674dc6b5a6a0ece419c40508835affcb7679a48f2a443777e829bd1e74.json +++ b/backend/.sqlx/query-fec6d5674dc6b5a6a0ece419c40508835affcb7679a48f2a443777e829bd1e74.json @@ -41,11 +41,11 @@ ] }, "nullable": [ - false, - false, - false, - false, - false, + true, + true, + true, + true, + true, true ] }, diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index cd33ef9651..0fb8d7a2a7 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -5a3f583656e7f754705ea3b5263593092cbba404 +391a67b7af2c874ee86971cc299ade066de00fc8 diff --git a/backend/migrations/20250808214101_instance_group_auto_add.down.sql b/backend/migrations/20250808214101_instance_group_auto_add.down.sql new file mode 100644 index 0000000000..4da259a6b1 --- /dev/null +++ b/backend/migrations/20250808214101_instance_group_auto_add.down.sql @@ -0,0 +1,4 @@ +-- Remove auto-add columns for instance groups +ALTER TABLE workspace_settings +DROP COLUMN auto_add_instance_groups, +DROP COLUMN auto_add_instance_groups_roles; diff --git a/backend/migrations/20250808214101_instance_group_auto_add.up.sql b/backend/migrations/20250808214101_instance_group_auto_add.up.sql new file mode 100644 index 0000000000..a97f00ca99 --- /dev/null +++ b/backend/migrations/20250808214101_instance_group_auto_add.up.sql @@ -0,0 +1,4 @@ +-- Add auto-add columns for instance groups +ALTER TABLE workspace_settings +ADD COLUMN auto_add_instance_groups text[] DEFAULT '{}', +ADD COLUMN auto_add_instance_groups_roles jsonb DEFAULT '{}'; diff --git a/backend/migrations/20250811171024_add_usr_added_via.down.sql b/backend/migrations/20250811171024_add_usr_added_via.down.sql new file mode 100644 index 0000000000..49cf62e51f --- /dev/null +++ b/backend/migrations/20250811171024_add_usr_added_via.down.sql @@ -0,0 +1,3 @@ +-- Remove added_via tracking +DROP INDEX idx_usr_added_via; +ALTER TABLE usr DROP COLUMN added_via; diff --git a/backend/migrations/20250811171024_add_usr_added_via.up.sql b/backend/migrations/20250811171024_add_usr_added_via.up.sql new file mode 100644 index 0000000000..6eb4741181 --- /dev/null +++ b/backend/migrations/20250811171024_add_usr_added_via.up.sql @@ -0,0 +1,8 @@ +-- Add added_via column to track how users were added to workspaces +-- NULL = manual addition +-- {"source": "domain", "domain": "company.com"} = domain auto-add +-- {"source": "instance_group", "group": "developers", "role": "developer"} = instance group auto-add +ALTER TABLE usr ADD COLUMN added_via jsonb DEFAULT NULL; + +-- Add index for efficient queries on added_via +CREATE INDEX idx_usr_added_via ON usr USING gin (added_via); diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 83906d0dd1..82eca9a9bd 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1944,6 +1944,14 @@ paths: type: boolean auto_add: type: boolean + auto_add_instance_groups: + type: array + items: + type: string + auto_add_instance_groups_roles: + type: object + additionalProperties: + type: string plan: type: string customer_id: @@ -2351,6 +2359,39 @@ paths: schema: type: string + /w/{workspace}/workspaces/edit_instance_groups: + post: + summary: edit instance groups + operationId: editInstanceGroups + tags: + - workspace + parameters: + - $ref: "#/components/parameters/WorkspaceId" + requestBody: + description: Instance Groups Configuration + required: true + content: + application/json: + schema: + type: object + properties: + groups: + type: array + items: + type: string + roles: + type: object + additionalProperties: + type: string + + responses: + "200": + description: status + content: + text/plain: + schema: + type: string + /w/{workspace}/workspaces/edit_webhook: post: summary: edit webhook @@ -10713,6 +10754,21 @@ paths: type: array items: $ref: "#/components/schemas/InstanceGroup" + /groups/list_with_workspaces: + get: + summary: list instance groups with workspace information + operationId: listInstanceGroupsWithWorkspaces + tags: + - group + responses: + "200": + description: instance group list with workspaces + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/InstanceGroupWithWorkspaces" /groups/get/{name}: get: @@ -14583,6 +14639,10 @@ components: type: array items: type: string + added_via: + nullable: true + allOf: + - $ref: "#/components/schemas/UserSource" required: - email - username @@ -14594,6 +14654,22 @@ components: - folders - folders_owners + UserSource: + type: object + properties: + source: + type: string + enum: [domain, instance_group, manual] + description: "How the user was added to the workspace" + domain: + type: string + description: "The domain used for auto-invite (when source is 'domain')" + group: + type: string + description: "The instance group name (when source is 'instance_group')" + required: + - source + UserUsage: type: object properties: @@ -16811,6 +16887,8 @@ components: InstanceGroup: type: object + required: + - name properties: name: type: string @@ -16820,6 +16898,34 @@ components: type: array items: type: string + + InstanceGroupWithWorkspaces: + type: object + required: + - name + properties: + name: + type: string + summary: + type: string + emails: + type: array + items: + type: string + workspaces: + type: array + items: + $ref: "#/components/schemas/WorkspaceInfo" + + WorkspaceInfo: + type: object + properties: + workspace_id: + type: string + workspace_name: + type: string + role: + type: string required: - name diff --git a/backend/windmill-api/src/groups.rs b/backend/windmill-api/src/groups.rs index d5da9c25f5..4c526ffe26 100644 --- a/backend/windmill-api/src/groups.rs +++ b/backend/windmill-api/src/groups.rs @@ -44,6 +44,7 @@ pub fn workspaced_service() -> Router { pub fn global_service() -> Router { Router::new() .route("/list", get(list_igroups)) + .route("/list_with_workspaces", get(list_igroups_with_workspaces)) .route("/get/:name", get(get_igroup)) .route("/create", post(create_igroup)) .route("/update/:name", post(update_igroup)) @@ -187,7 +188,7 @@ pub async fn require_is_owner( ) -> Result<()> { let is_owner = query_scalar!( "SELECT EXISTS(SELECT 1 FROM group_ WHERE (group_.extra_perms ->> CONCAT('u/', $1::text))::boolean AND name = $2 AND workspace_id = $4) OR exists( - SELECT 1 FROM group_ g, jsonb_each_text(g.extra_perms) f + SELECT 1 FROM group_ g, jsonb_each_text(g.extra_perms) f WHERE $2 = g.name AND $4 = g.workspace_id AND SPLIT_PART(key, '/', 1) = 'g' AND key = ANY($3::text[]) AND value::boolean)", username, @@ -418,7 +419,7 @@ async fn get_group( let group = not_found_if_none(get_group_opt(&mut tx, &w_id, &name).await?, "Group", &name)?; let members = sqlx::query_scalar!( - "SELECT usr.username + "SELECT usr.username FROM usr_to_group LEFT JOIN usr ON usr_to_group.usr = usr.username AND usr_to_group.workspace_id = $2 WHERE group_ = $1 AND usr.workspace_id = $2 AND usr_to_group.workspace_id = $2", name, @@ -638,6 +639,21 @@ struct IGroup { summary: Option, emails: Option>, } + +#[derive(Serialize)] +struct IGroupWithWorkspaces { + name: String, + summary: Option, + emails: Option>, + workspaces: Vec, +} + +#[derive(Serialize, Clone)] +struct WorkspaceInfo { + workspace_id: String, + workspace_name: String, + role: String, +} async fn list_igroups(Extension(db): Extension) -> JsonResult> { let mut tx: Transaction<'_, Postgres> = db.begin().await?; @@ -652,6 +668,70 @@ async fn list_igroups(Extension(db): Extension) -> JsonResult> { return Ok(Json(groups)); } +async fn list_igroups_with_workspaces(Extension(db): Extension) -> JsonResult> { + let mut tx: Transaction<'_, Postgres> = db.begin().await?; + + // Get all instance groups with their emails first + let groups = sqlx::query_as!( + IGroup, + "SELECT name, summary, array_remove(array_agg(email_to_igroup.email), null) as emails FROM email_to_igroup RIGHT JOIN instance_group ON instance_group.name = email_to_igroup.igroup GROUP BY name, summary" + ) + .fetch_all(&mut *tx) + .await?; + + // Get all workspace mappings for instance groups in a single query + let workspace_mappings = sqlx::query!( + r#" + SELECT + ig.name as group_name, + ws.workspace_id, + w.name as workspace_name, + ws.auto_add_instance_groups_roles->ig.name as role + FROM instance_group ig + INNER JOIN workspace_settings ws ON ws.auto_add_instance_groups IS NOT NULL + AND ig.name = ANY(ws.auto_add_instance_groups) + INNER JOIN workspace w ON w.id = ws.workspace_id AND w.deleted = false + ORDER BY ig.name, ws.workspace_id + "# + ) + .fetch_all(&mut *tx) + .await?; + + // Create a map of group_name -> Vec + let mut workspaces_by_group: std::collections::HashMap> = std::collections::HashMap::new(); + for mapping in workspace_mappings { + let role = mapping.role + .and_then(|r| r.as_str().map(|s| s.to_string())) + .unwrap_or_else(|| "developer".to_string()); + + let workspace_info = WorkspaceInfo { + workspace_id: mapping.workspace_id.clone(), + workspace_name: mapping.workspace_name, + role, + }; + + workspaces_by_group + .entry(mapping.group_name) + .or_insert_with(Vec::new) + .push(workspace_info); + } + + let mut result = Vec::new(); + for group in groups { + let workspaces = workspaces_by_group.get(&group.name).cloned().unwrap_or_default(); + + result.push(IGroupWithWorkspaces { + name: group.name, + summary: group.summary, + emails: group.emails, + workspaces, + }); + } + + tx.commit().await?; + return Ok(Json(result)); +} + async fn get_igroup(Path(name): Path, Extension(db): Extension) -> JsonResult { let group = sqlx::query_as!( IGroup, diff --git a/backend/windmill-api/src/users.rs b/backend/windmill-api/src/users.rs index 4692eea71a..16d969c236 100644 --- a/backend/windmill-api/src/users.rs +++ b/backend/windmill-api/src/users.rs @@ -9,6 +9,7 @@ #![allow(non_snake_case)] use quick_cache::sync::Cache; +use sqlx::{Postgres, Transaction}; use std::sync::atomic::AtomicBool; use std::sync::Arc; @@ -262,6 +263,8 @@ pub struct User { pub operator: bool, pub disabled: bool, pub role: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub added_via: Option, } #[derive(Serialize)] @@ -494,7 +497,7 @@ async fn list_user_usage( WHERE workspace_id = $1 AND job_kind NOT IN ('flow', 'flowpreview', 'flownode') AND email = usr.email - AND now() - '1 week'::interval < created_at + AND now() - '1 week'::interval < created_at ) usage WHERE workspace_id = $1 ", @@ -786,8 +789,8 @@ async fn get_usage( ) -> Result { let usage = sqlx::query_scalar!( " - SELECT usage.usage FROM usage - WHERE is_workspace = false + SELECT usage.usage FROM usage + WHERE is_workspace = false AND month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date) AND id = $1", email @@ -810,6 +813,8 @@ pub struct User2 { pub role: Option, pub super_admin: bool, pub name: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub added_via: Option, } async fn get_user(w_id: &str, username: &str, db: &DB) -> Result> { @@ -1315,49 +1320,15 @@ async fn update_workspace_user( require_admin(authed.is_admin, &authed.username)?; - if let Some(a) = eu.is_admin { - sqlx::query_scalar!( - "UPDATE usr SET is_admin = $1 WHERE username = $2 AND workspace_id = $3", - a, - &username_to_update, - &w_id - ) - .execute(&mut *tx) - .await?; - } - - if let Some(a) = eu.operator { - sqlx::query_scalar!( - "UPDATE usr SET operator = $1 WHERE username = $2 AND workspace_id = $3", - a, - &username_to_update, - &w_id - ) - .execute(&mut *tx) - .await?; - } - - if let Some(a) = eu.disabled { - sqlx::query_scalar!( - "UPDATE usr SET disabled = $1 WHERE username = $2 AND workspace_id = $3", - a, - &username_to_update, - &w_id - ) - .execute(&mut *tx) - .await?; - } - - audit_log( - &mut *tx, - &authed, - "users.update", - ActionKind::Update, + update_workspace_user_internal( &w_id, - Some(&username_to_update), - None, - ) - .await?; + &username_to_update, + eu.is_admin, + eu.operator, + eu.disabled, + &mut tx, + Some(&authed) + ).await?; let user_email = sqlx::query_scalar!( "SELECT email FROM usr WHERE username = $1 AND workspace_id = $2", @@ -1500,6 +1471,105 @@ async fn create_user( crate::users_oss::create_user(authed, db, webhook, argon2, nu).await } +/// Internal helper for updating workspace user permissions - used by both API and system operations +pub async fn update_workspace_user_internal( + w_id: &str, + username_to_update: &str, + is_admin: Option, + operator: Option, + disabled: Option, + tx: &mut Transaction<'_, Postgres>, + authed: Option<&ApiAuthed>, // None for system operations +) -> Result<()> { + if let Some(a) = is_admin { + sqlx::query_scalar!( + "UPDATE usr SET is_admin = $1 WHERE username = $2 AND workspace_id = $3", + a, + username_to_update, + w_id + ) + .execute(&mut **tx) + .await?; + } + if let Some(a) = operator { + sqlx::query_scalar!( + "UPDATE usr SET operator = $1 WHERE username = $2 AND workspace_id = $3", + a, + username_to_update, + w_id + ) + .execute(&mut **tx) + .await?; + } + if let Some(a) = disabled { + sqlx::query_scalar!( + "UPDATE usr SET disabled = $1 WHERE username = $2 AND workspace_id = $3", + a, + username_to_update, + w_id + ) + .execute(&mut **tx) + .await?; + } + + // Only audit if we have an authenticated user (API calls) + if let Some(auth) = authed { + audit_log( + &mut **tx, + auth, + "users.update", + ActionKind::Update, + w_id, + Some(username_to_update), + None, + ) + .await?; + } + + Ok(()) +} + +/// Internal helper for deleting workspace users - used by both API and system operations +pub async fn delete_workspace_user_internal( + w_id: &str, + username_to_delete: &str, + email_to_delete: &str, + tx: &mut Transaction<'_, Postgres>, + authed: Option<&ApiAuthed>, // None for system operations +) -> Result<()> { + sqlx::query_scalar!( + "DELETE FROM usr WHERE email = $1 AND workspace_id = $2", + email_to_delete, + w_id + ) + .execute(&mut **tx) + .await?; + + sqlx::query!( + "DELETE FROM usr_to_group WHERE usr = $1 AND workspace_id = $2", + username_to_delete, + w_id + ) + .execute(&mut **tx) + .await?; + + // Only audit if we have an authenticated user (API calls) + if let Some(auth) = authed { + audit_log( + &mut **tx, + auth, + "users.delete", + ActionKind::Delete, + w_id, + Some(username_to_delete), + None, + ) + .await?; + } + + Ok(()) +} + async fn delete_workspace_user( authed: ApiAuthed, Extension(db): Extension, @@ -1519,32 +1589,7 @@ async fn delete_workspace_user( let email_to_delete = not_found_if_none(email_to_delete_o, "User", &username_to_delete)?; - sqlx::query_scalar!( - "DELETE FROM usr WHERE email = $1 AND workspace_id = $2", - email_to_delete, - &w_id - ) - .execute(&mut *tx) - .await?; - - sqlx::query!( - "DELETE FROM usr_to_group WHERE usr = $1 AND workspace_id = $2", - &username_to_delete, - &w_id - ) - .execute(&mut *tx) - .await?; - - audit_log( - &mut *tx, - &authed, - "users.delete", - ActionKind::Delete, - &w_id, - Some(&username_to_delete), - None, - ) - .await?; + delete_workspace_user_internal(&w_id, &username_to_delete, &email_to_delete, &mut tx, Some(&authed)).await?; tx.commit().await?; handle_deployment_metadata( @@ -2096,8 +2141,8 @@ async fn get_all_runnables( })?; let mut tx = db.clone().begin(&nauthed).await?; let flows = sqlx::query!( - "SELECT flow.workspace_id as workspace, flow.path, summary, description, flow_version.schema - FROM flow + "SELECT flow.workspace_id as workspace, flow.path, summary, description, flow_version.schema + FROM flow LEFT JOIN flow_version ON flow_version.id = flow.versions[array_upper(flow.versions, 1)] WHERE flow.workspace_id = $1", workspace @@ -2580,9 +2625,9 @@ async fn update_username_in_workpsace<'c>( // ---- flows ---- sqlx::query!( r#"INSERT INTO flow - (workspace_id, path, summary, description, archived, extra_perms, dependency_job, draft_only, tag, ws_error_handler_muted, dedicated_worker, timeout, visible_to_runner_only, on_behalf_of_email, concurrency_key, versions, value, schema, edited_by, edited_at) + (workspace_id, path, summary, description, archived, extra_perms, dependency_job, draft_only, tag, ws_error_handler_muted, dedicated_worker, timeout, visible_to_runner_only, on_behalf_of_email, concurrency_key, versions, value, schema, edited_by, edited_at) SELECT workspace_id, REGEXP_REPLACE(path,'u/' || $2 || '/(.*)','u/' || $1 || '/\1'), summary, description, archived, extra_perms, dependency_job, draft_only, tag, ws_error_handler_muted, dedicated_worker, timeout, visible_to_runner_only, on_behalf_of_email, concurrency_key, versions, value, schema, edited_by, edited_at - FROM flow + FROM flow WHERE path LIKE ('u/' || $2 || '/%') AND workspace_id = $3"#, new_username, old_username, diff --git a/backend/windmill-api/src/workspaces.rs b/backend/windmill-api/src/workspaces.rs index acec13e5f0..78052b8ed8 100644 --- a/backend/windmill-api/src/workspaces.rs +++ b/backend/windmill-api/src/workspaces.rs @@ -101,6 +101,7 @@ pub fn workspaced_service() -> Router { ) .route("/edit_webhook", post(edit_webhook)) .route("/edit_auto_invite", post(edit_auto_invite)) + .route("/edit_instance_groups", post(edit_instance_groups)) .route("/edit_deploy_to", post(edit_deploy_to)) .route( "/get_secondary_storage_names", @@ -252,6 +253,10 @@ pub struct WorkspaceSettings { pub operator_settings: Option, #[serde(skip_serializing_if = "Option::is_none")] pub git_app_installations: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub auto_add_instance_groups: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub auto_add_instance_groups_roles: Option, } #[derive(sqlx::Type, Serialize, Deserialize, Debug)] @@ -462,7 +467,7 @@ async fn get_settings( let settings = sqlx::query_as!( WorkspaceSettings, r#" - SELECT + SELECT workspace_id, slack_team_id, teams_team_id, @@ -491,10 +496,12 @@ async fn get_settings( mute_critical_alerts, color, operator_settings, - git_app_installations - FROM + git_app_installations, + auto_add_instance_groups, + auto_add_instance_groups_roles + FROM workspace_settings - WHERE + WHERE workspace_id = $1 "#, &w_id @@ -726,6 +733,28 @@ async fn edit_auto_invite( crate::workspaces_oss::edit_auto_invite(authed, db, w_id, ea).await } +#[cfg(feature = "private")] +async fn edit_instance_groups( + authed: ApiAuthed, + Extension(db): Extension, + Path(w_id): Path, + Json(config): Json, +) -> Result { + crate::workspaces_ee::edit_instance_groups(authed, db, w_id, config).await +} + +#[cfg(not(feature = "private"))] +async fn edit_instance_groups( + _authed: ApiAuthed, + Extension(_db): Extension, + Path(_w_id): Path, + Json(_config): Json, +) -> Result { + Err(Error::BadRequest( + "Instance groups are only available on Windmill Enterprise Edition".to_string(), + )) +} + async fn edit_webhook( authed: ApiAuthed, Extension(db): Extension, @@ -1716,13 +1745,13 @@ async fn edit_error_handler( sqlx::query!( r#" - UPDATE + UPDATE workspace_settings SET error_handler = $1, error_handler_extra_args = $2, error_handler_muted_on_cancel = $3 - WHERE + WHERE workspace_id = $4 "#, error_handler, @@ -1735,13 +1764,13 @@ async fn edit_error_handler( } else { sqlx::query!( r#" - UPDATE + UPDATE workspace_settings SET error_handler = NULL, error_handler_extra_args = NULL, error_handler_muted_on_cancel = NULL - WHERE + WHERE workspace_id = $1 "#, &w_id diff --git a/frontend/src/lib/components/settings/WorkspaceUserSettings.svelte b/frontend/src/lib/components/settings/WorkspaceUserSettings.svelte index 8adbc4f7b3..48dc119171 100644 --- a/frontend/src/lib/components/settings/WorkspaceUserSettings.svelte +++ b/frontend/src/lib/components/settings/WorkspaceUserSettings.svelte @@ -13,10 +13,11 @@ import Toggle from '$lib/components/Toggle.svelte' import Tooltip from '$lib/components/Tooltip.svelte' import type { CancelablePromise, User, UserUsage } from '$lib/gen' - import { UserService, WorkspaceService, type WorkspaceInvite } from '$lib/gen' + import { UserService, WorkspaceService, GroupService, type WorkspaceInvite } from '$lib/gen' import { userStore, workspaceStore } from '$lib/stores' import { sendUserToast } from '$lib/toast' - import { Loader2, Mails, Search } from 'lucide-svelte' + import { Loader2, Mails, Search, Plus } from 'lucide-svelte' + import Select from '$lib/components/select/Select.svelte' import SearchItems from '../SearchItems.svelte' import Cell from '../table/Cell.svelte' import Row from '../table/Row.svelte' @@ -34,11 +35,47 @@ let autoAdd: boolean | undefined = $state(undefined) let nbDisplayed = $state(30) + // Instance group auto-add settings + let instanceGroups: Array<{name: string, summary?: string, emails?: string[]}> = $state([]) + let autoAddInstanceGroups: string[] = $state([]) + let autoAddInstanceGroupsRoles: Record = $state({}) + + // Add new instance group form state + let selectedNewInstanceGroup: string | undefined = $state(undefined) + let selectedNewRole: string | undefined = $state('developer') + + // Available groups for dropdowns - filter out already configured groups + let availableGroupItems = $derived( + instanceGroups + .filter(group => !autoAddInstanceGroups.includes(group.name)) + .map(group => ({ + value: group.name, + label: group.name + (group.summary ? ` - ${group.summary}` : '') + })) + ) + + // Sort users so manual users come first, then instance group users + let sortedUsers = $derived(() => { + const userList = (filteredUsers || users || []).slice() + return userList.sort((a: User, b: User) => { + const aIsInstanceGroup = a.added_via?.source === 'instance_group' ? 1 : 0 + const bIsInstanceGroup = b.added_via?.source === 'instance_group' ? 1 : 0 + return aIsInstanceGroup - bIsInstanceGroup + }) + }) + + let hasNonManualUsers = $derived( + (filteredUsers || users || []).some((user: User) => user.added_via?.source === 'instance_group' || user.added_via?.source === 'domain') + ) + + async function loadSettings(): Promise { const settings = await WorkspaceService.getSettings({ workspace: $workspaceStore! }) auto_invite_domain = settings.auto_invite_domain operatorOnly = settings.auto_invite_operator autoAdd = settings.auto_add + autoAddInstanceGroups = settings.auto_add_instance_groups || [] + autoAddInstanceGroupsRoles = settings.auto_add_instance_groups_roles || {} } let getUsagePromise: CancelablePromise | undefined = undefined @@ -77,6 +114,87 @@ allowedAutoDomain = await WorkspaceService.isDomainAllowed() } + async function loadInstanceGroups(): Promise { + try { + instanceGroups = await GroupService.listInstanceGroups() + } catch (e) { + console.warn('Failed to load instance groups:', e) + instanceGroups = [] + } + } + + async function saveInstanceGroupSettings(): Promise { + try { + await WorkspaceService.editInstanceGroups({ + workspace: $workspaceStore ?? '', + requestBody: { + groups: autoAddInstanceGroups, + roles: autoAddInstanceGroupsRoles + } + }) + sendUserToast('Instance group settings saved') + // Refresh user list to show newly auto-added users + listUsers() + } catch (e) { + console.error('Failed to save instance group settings:', e) + sendUserToast('Failed to save settings', true) + } + } + + async function addInstanceGroup(): Promise { + if (!selectedNewInstanceGroup || !selectedNewRole) return + + const groupToAdd = selectedNewInstanceGroup + const roleToAdd = selectedNewRole + + try { + autoAddInstanceGroups = [...autoAddInstanceGroups, groupToAdd] + autoAddInstanceGroupsRoles[groupToAdd] = roleToAdd + + // Reset form + selectedNewInstanceGroup = undefined + selectedNewRole = 'developer' + + await saveInstanceGroupSettings() + } catch (e) { + // Rollback on error + autoAddInstanceGroups = autoAddInstanceGroups.filter(g => g !== groupToAdd) + delete autoAddInstanceGroupsRoles[groupToAdd] + sendUserToast('Failed to add instance group', true) + } + } + + async function removeInstanceGroup(groupName: string): Promise { + const previousGroups = [...autoAddInstanceGroups] + const previousRole = autoAddInstanceGroupsRoles[groupName] + + try { + autoAddInstanceGroups = autoAddInstanceGroups.filter(g => g !== groupName) + delete autoAddInstanceGroupsRoles[groupName] + await saveInstanceGroupSettings() + } catch (e) { + // Rollback on error + autoAddInstanceGroups = previousGroups + if (previousRole) { + autoAddInstanceGroupsRoles[groupName] = previousRole + } + sendUserToast('Failed to remove instance group', true) + } + } + + async function updateGroupRole(groupName: string, role: string): Promise { + const previousRole = autoAddInstanceGroupsRoles[groupName] + + try { + autoAddInstanceGroupsRoles[groupName] = role + await saveInstanceGroupSettings() + } catch (e) { + // Rollback on error + autoAddInstanceGroupsRoles[groupName] = previousRole + sendUserToast('Failed to update role', true) + } + } + let domain = $derived($userStore?.email.split('@')[1]) $effect(() => { @@ -87,6 +205,7 @@ getUsage() listInvites() loadSettings() + loadInstanceGroups() }) } }) @@ -100,6 +219,7 @@ }) let deleteConfirmedCallback: (() => void) | undefined = $state(undefined) + let removeInstanceGroupConfirmedCallback: (() => void) | undefined = $state(undefined) async function removeAllInvitesFromDomain() { await Promise.all( @@ -274,7 +394,10 @@ invite_all: !isCloudHosted(), auto_add: showInvites ? (autoAdd ?? false) : true } - : { operator: undefined, auto_add: undefined } + : { + operator: undefined, + auto_add: undefined + } }) loadSettings() listInvites() @@ -292,6 +415,179 @@ {/snippet} + + {#if instanceGroups.length > 0} + + {#snippet trigger()} + + {/snippet} + {#snippet content()} +
+
+ + Auto-add instance groups + + + + {#if availableGroupItems.length > 0} +
+
+
+ Instance group +